| /* |
| * Copyright (C) 2007 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.deskclock; |
| |
| import android.animation.Animator; |
| import android.animation.Animator.AnimatorListener; |
| import android.animation.AnimatorListenerAdapter; |
| import android.animation.ValueAnimator; |
| import android.app.Activity; |
| import android.app.Fragment; |
| import android.app.FragmentTransaction; |
| import android.app.LoaderManager; |
| import android.app.TimePickerDialog.OnTimeSetListener; |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.Loader; |
| import android.content.res.Configuration; |
| import android.content.res.Resources; |
| import android.database.Cursor; |
| import android.database.DataSetObserver; |
| import android.graphics.Color; |
| import android.graphics.Rect; |
| import android.graphics.Typeface; |
| import android.media.Ringtone; |
| import android.media.RingtoneManager; |
| import android.net.Uri; |
| import android.os.AsyncTask; |
| import android.os.Bundle; |
| import android.os.Vibrator; |
| import android.transition.AutoTransition; |
| import android.transition.Fade; |
| import android.transition.Transition; |
| import android.transition.TransitionManager; |
| import android.transition.TransitionSet; |
| import android.view.LayoutInflater; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.view.View.OnClickListener; |
| import android.view.ViewGroup; |
| import android.view.ViewGroup.LayoutParams; |
| import android.view.ViewTreeObserver; |
| import android.view.animation.AccelerateDecelerateInterpolator; |
| import android.view.animation.DecelerateInterpolator; |
| import android.view.animation.Interpolator; |
| import android.widget.Button; |
| import android.widget.CheckBox; |
| import android.widget.CompoundButton; |
| import android.widget.CursorAdapter; |
| import android.widget.FrameLayout; |
| import android.widget.ImageButton; |
| import android.widget.LinearLayout; |
| import android.widget.ListView; |
| import android.widget.Switch; |
| import android.widget.TextView; |
| import android.widget.TimePicker; |
| import android.widget.Toast; |
| |
| import com.android.deskclock.alarms.AlarmStateManager; |
| import com.android.deskclock.provider.Alarm; |
| import com.android.deskclock.provider.AlarmInstance; |
| import com.android.deskclock.provider.DaysOfWeek; |
| import com.android.deskclock.widget.ActionableToastBar; |
| import com.android.deskclock.widget.TextTime; |
| |
| import java.text.DateFormatSymbols; |
| import java.util.Calendar; |
| import java.util.HashSet; |
| |
| /** |
| * AlarmClock application. |
| */ |
| public class AlarmClockFragment extends DeskClockFragment implements |
| LoaderManager.LoaderCallbacks<Cursor>, OnTimeSetListener, View.OnTouchListener { |
| private static final float EXPAND_DECELERATION = 1f; |
| private static final float COLLAPSE_DECELERATION = 0.7f; |
| |
| private static final int ANIMATION_DURATION = 300; |
| private static final int EXPAND_DURATION = 300; |
| private static final int COLLAPSE_DURATION = 250; |
| |
| private static final int ROTATE_180_DEGREE = 180; |
| private static final float ALARM_ELEVATION = 8f; |
| private static final float TINTED_LEVEL = 0.09f; |
| |
| private static final String KEY_EXPANDED_ID = "expandedId"; |
| private static final String KEY_REPEAT_CHECKED_IDS = "repeatCheckedIds"; |
| private static final String KEY_RINGTONE_TITLE_CACHE = "ringtoneTitleCache"; |
| private static final String KEY_SELECTED_ALARMS = "selectedAlarms"; |
| private static final String KEY_DELETED_ALARM = "deletedAlarm"; |
| private static final String KEY_UNDO_SHOWING = "undoShowing"; |
| private static final String KEY_PREVIOUS_DAY_MAP = "previousDayMap"; |
| private static final String KEY_SELECTED_ALARM = "selectedAlarm"; |
| private static final DeskClockExtensions sDeskClockExtensions = ExtensionsFactory |
| .getDeskClockExtensions(); |
| |
| private static final int REQUEST_CODE_RINGTONE = 1; |
| private static final long INVALID_ID = -1; |
| |
| // This extra is used when receiving an intent to create an alarm, but no alarm details |
| // have been passed in, so the alarm page should start the process of creating a new alarm. |
| public static final String ALARM_CREATE_NEW_INTENT_EXTRA = "deskclock.create.new"; |
| |
| // This extra is used when receiving an intent to scroll to specific alarm. If alarm |
| // can not be found, and toast message will pop up that the alarm has be deleted. |
| public static final String SCROLL_TO_ALARM_INTENT_EXTRA = "deskclock.scroll.to.alarm"; |
| |
| private FrameLayout mMainLayout; |
| private ListView mAlarmsList; |
| private AlarmItemAdapter mAdapter; |
| private View mEmptyView; |
| private View mFooterView; |
| |
| private Bundle mRingtoneTitleCache; // Key: ringtone uri, value: ringtone title |
| private ActionableToastBar mUndoBar; |
| private View mUndoFrame; |
| |
| private Alarm mSelectedAlarm; |
| private long mScrollToAlarmId = INVALID_ID; |
| |
| private Loader mCursorLoader = null; |
| |
| // Saved states for undo |
| private Alarm mDeletedAlarm; |
| private Alarm mAddedAlarm; |
| private boolean mUndoShowing; |
| |
| private Interpolator mExpandInterpolator; |
| private Interpolator mCollapseInterpolator; |
| |
| private Transition mAddRemoveTransition; |
| private Transition mRepeatTransition; |
| private Transition mEmptyViewTransition; |
| |
| public AlarmClockFragment() { |
| // Basic provider required by Fragment.java |
| } |
| |
| @Override |
| public void onCreate(Bundle savedState) { |
| super.onCreate(savedState); |
| mCursorLoader = getLoaderManager().initLoader(0, null, this); |
| } |
| |
| @Override |
| public View onCreateView(LayoutInflater inflater, ViewGroup container, |
| Bundle savedState) { |
| // Inflate the layout for this fragment |
| final View v = inflater.inflate(R.layout.alarm_clock, container, false); |
| |
| long expandedId = INVALID_ID; |
| long[] repeatCheckedIds = null; |
| long[] selectedAlarms = null; |
| Bundle previousDayMap = null; |
| if (savedState != null) { |
| expandedId = savedState.getLong(KEY_EXPANDED_ID); |
| repeatCheckedIds = savedState.getLongArray(KEY_REPEAT_CHECKED_IDS); |
| mRingtoneTitleCache = savedState.getBundle(KEY_RINGTONE_TITLE_CACHE); |
| mDeletedAlarm = savedState.getParcelable(KEY_DELETED_ALARM); |
| mUndoShowing = savedState.getBoolean(KEY_UNDO_SHOWING); |
| selectedAlarms = savedState.getLongArray(KEY_SELECTED_ALARMS); |
| previousDayMap = savedState.getBundle(KEY_PREVIOUS_DAY_MAP); |
| mSelectedAlarm = savedState.getParcelable(KEY_SELECTED_ALARM); |
| } |
| |
| mExpandInterpolator = new DecelerateInterpolator(EXPAND_DECELERATION); |
| mCollapseInterpolator = new DecelerateInterpolator(COLLAPSE_DECELERATION); |
| |
| mAddRemoveTransition = new AutoTransition(); |
| mAddRemoveTransition.setDuration(ANIMATION_DURATION); |
| |
| mRepeatTransition = new AutoTransition(); |
| mRepeatTransition.setDuration(ANIMATION_DURATION / 2); |
| mRepeatTransition.setInterpolator(new AccelerateDecelerateInterpolator()); |
| |
| mEmptyViewTransition = new TransitionSet() |
| .setOrdering(TransitionSet.ORDERING_SEQUENTIAL) |
| .addTransition(new Fade(Fade.OUT)) |
| .addTransition(new Fade(Fade.IN)) |
| .setDuration(ANIMATION_DURATION); |
| |
| boolean isLandscape = getResources().getConfiguration().orientation |
| == Configuration.ORIENTATION_LANDSCAPE; |
| View menuButton = v.findViewById(R.id.menu_button); |
| if (menuButton != null) { |
| if (isLandscape) { |
| menuButton.setVisibility(View.GONE); |
| } else { |
| menuButton.setVisibility(View.VISIBLE); |
| setupFakeOverflowMenuButton(menuButton); |
| } |
| } |
| |
| mEmptyView = v.findViewById(R.id.alarms_empty_view); |
| |
| mMainLayout = (FrameLayout) v.findViewById(R.id.main); |
| mAlarmsList = (ListView) v.findViewById(R.id.alarms_list); |
| |
| mUndoBar = (ActionableToastBar) v.findViewById(R.id.undo_bar); |
| mUndoFrame = v.findViewById(R.id.undo_frame); |
| mUndoFrame.setOnTouchListener(this); |
| |
| mFooterView = v.findViewById(R.id.alarms_footer_view); |
| mFooterView.setOnTouchListener(this); |
| |
| mAdapter = new AlarmItemAdapter(getActivity(), |
| expandedId, repeatCheckedIds, selectedAlarms, previousDayMap, mAlarmsList); |
| mAdapter.registerDataSetObserver(new DataSetObserver() { |
| |
| private int prevAdapterCount = -1; |
| |
| @Override |
| public void onChanged() { |
| |
| final int count = mAdapter.getCount(); |
| if (mDeletedAlarm != null && prevAdapterCount > count) { |
| showUndoBar(); |
| } |
| |
| if ((count == 0 && prevAdapterCount > 0) || /* should fade in */ |
| (count > 0 && prevAdapterCount == 0) /* should fade out */) { |
| TransitionManager.beginDelayedTransition(mMainLayout, mEmptyViewTransition); |
| } |
| mEmptyView.setVisibility(count == 0 ? View.VISIBLE : View.GONE); |
| |
| // Cache this adapter's count for when the adapter changes. |
| prevAdapterCount = count; |
| super.onChanged(); |
| } |
| }); |
| |
| if (mRingtoneTitleCache == null) { |
| mRingtoneTitleCache = new Bundle(); |
| } |
| |
| mAlarmsList.setAdapter(mAdapter); |
| mAlarmsList.setVerticalScrollBarEnabled(true); |
| mAlarmsList.setOnCreateContextMenuListener(this); |
| |
| if (mUndoShowing) { |
| showUndoBar(); |
| } |
| return v; |
| } |
| |
| private void setUndoBarRightMargin(int margin) { |
| FrameLayout.LayoutParams params = |
| (FrameLayout.LayoutParams) mUndoBar.getLayoutParams(); |
| ((FrameLayout.LayoutParams) mUndoBar.getLayoutParams()) |
| .setMargins(params.leftMargin, params.topMargin, margin, params.bottomMargin); |
| mUndoBar.requestLayout(); |
| } |
| |
| @Override |
| public void onResume() { |
| super.onResume(); |
| |
| final DeskClock activity = (DeskClock) getActivity(); |
| if (activity.getSelectedTab() == DeskClock.ALARM_TAB_INDEX) { |
| setFabAppearance(); |
| setLeftRightButtonAppearance(); |
| } |
| |
| if (mAdapter != null) { |
| mAdapter.notifyDataSetChanged(); |
| } |
| // Check if another app asked us to create a blank new alarm. |
| final Intent intent = getActivity().getIntent(); |
| if (intent.hasExtra(ALARM_CREATE_NEW_INTENT_EXTRA)) { |
| if (intent.getBooleanExtra(ALARM_CREATE_NEW_INTENT_EXTRA, false)) { |
| // An external app asked us to create a blank alarm. |
| startCreatingAlarm(); |
| } |
| |
| // Remove the CREATE_NEW extra now that we've processed it. |
| intent.removeExtra(ALARM_CREATE_NEW_INTENT_EXTRA); |
| } else if (intent.hasExtra(SCROLL_TO_ALARM_INTENT_EXTRA)) { |
| long alarmId = intent.getLongExtra(SCROLL_TO_ALARM_INTENT_EXTRA, Alarm.INVALID_ID); |
| if (alarmId != Alarm.INVALID_ID) { |
| mScrollToAlarmId = alarmId; |
| if (mCursorLoader != null && mCursorLoader.isStarted()) { |
| // We need to force a reload here to make sure we have the latest view |
| // of the data to scroll to. |
| mCursorLoader.forceLoad(); |
| } |
| } |
| |
| // Remove the SCROLL_TO_ALARM extra now that we've processed it. |
| intent.removeExtra(SCROLL_TO_ALARM_INTENT_EXTRA); |
| } |
| } |
| |
| private void hideUndoBar(boolean animate, MotionEvent event) { |
| if (mUndoBar != null) { |
| mUndoFrame.setVisibility(View.GONE); |
| if (event != null && mUndoBar.isEventInToastBar(event)) { |
| // Avoid touches inside the undo bar. |
| return; |
| } |
| mUndoBar.hide(animate); |
| } |
| mDeletedAlarm = null; |
| mUndoShowing = false; |
| } |
| |
| private void showUndoBar() { |
| final Alarm deletedAlarm = mDeletedAlarm; |
| mUndoFrame.setVisibility(View.VISIBLE); |
| mUndoBar.show(new ActionableToastBar.ActionClickedListener() { |
| @Override |
| public void onActionClicked() { |
| mAddedAlarm = deletedAlarm; |
| mDeletedAlarm = null; |
| mUndoShowing = false; |
| |
| asyncAddAlarm(deletedAlarm); |
| } |
| }, 0, getResources().getString(R.string.alarm_deleted), true, R.string.alarm_undo, true); |
| } |
| |
| @Override |
| public void onSaveInstanceState(Bundle outState) { |
| super.onSaveInstanceState(outState); |
| outState.putLong(KEY_EXPANDED_ID, mAdapter.getExpandedId()); |
| outState.putLongArray(KEY_REPEAT_CHECKED_IDS, mAdapter.getRepeatArray()); |
| outState.putLongArray(KEY_SELECTED_ALARMS, mAdapter.getSelectedAlarmsArray()); |
| outState.putBundle(KEY_RINGTONE_TITLE_CACHE, mRingtoneTitleCache); |
| outState.putParcelable(KEY_DELETED_ALARM, mDeletedAlarm); |
| outState.putBoolean(KEY_UNDO_SHOWING, mUndoShowing); |
| outState.putBundle(KEY_PREVIOUS_DAY_MAP, mAdapter.getPreviousDaysOfWeekMap()); |
| outState.putParcelable(KEY_SELECTED_ALARM, mSelectedAlarm); |
| } |
| |
| @Override |
| public void onDestroy() { |
| super.onDestroy(); |
| ToastMaster.cancelToast(); |
| } |
| |
| @Override |
| public void onPause() { |
| super.onPause(); |
| // When the user places the app in the background by pressing "home", |
| // dismiss the toast bar. However, since there is no way to determine if |
| // home was pressed, just dismiss any existing toast bar when restarting |
| // the app. |
| hideUndoBar(false, null); |
| } |
| |
| // Callback used by TimePickerDialog |
| @Override |
| public void onTimeSet(TimePicker timePicker, int hourOfDay, int minute) { |
| if (mSelectedAlarm == null) { |
| // If mSelectedAlarm is null then we're creating a new alarm. |
| Alarm a = new Alarm(); |
| a.alert = RingtoneManager.getActualDefaultRingtoneUri(getActivity(), |
| RingtoneManager.TYPE_ALARM); |
| if (a.alert == null) { |
| a.alert = Uri.parse("content://settings/system/alarm_alert"); |
| } |
| a.hour = hourOfDay; |
| a.minutes = minute; |
| a.enabled = true; |
| mAddedAlarm = a; |
| asyncAddAlarm(a); |
| } else { |
| mSelectedAlarm.hour = hourOfDay; |
| mSelectedAlarm.minutes = minute; |
| mSelectedAlarm.enabled = true; |
| mScrollToAlarmId = mSelectedAlarm.id; |
| asyncUpdateAlarm(mSelectedAlarm, true); |
| mSelectedAlarm = null; |
| } |
| } |
| |
| private void showLabelDialog(final Alarm alarm) { |
| final FragmentTransaction ft = getFragmentManager().beginTransaction(); |
| final Fragment prev = getFragmentManager().findFragmentByTag("label_dialog"); |
| if (prev != null) { |
| ft.remove(prev); |
| } |
| ft.addToBackStack(null); |
| |
| // Create and show the dialog. |
| final LabelDialogFragment newFragment = |
| LabelDialogFragment.newInstance(alarm, alarm.label, getTag()); |
| newFragment.show(ft, "label_dialog"); |
| } |
| |
| public void setLabel(Alarm alarm, String label) { |
| alarm.label = label; |
| asyncUpdateAlarm(alarm, false); |
| } |
| |
| @Override |
| public Loader<Cursor> onCreateLoader(int id, Bundle args) { |
| return Alarm.getAlarmsCursorLoader(getActivity()); |
| } |
| |
| @Override |
| public void onLoadFinished(Loader<Cursor> cursorLoader, final Cursor data) { |
| mAdapter.swapCursor(data); |
| if (mScrollToAlarmId != INVALID_ID) { |
| scrollToAlarm(mScrollToAlarmId); |
| mScrollToAlarmId = INVALID_ID; |
| } |
| } |
| |
| /** |
| * Scroll to alarm with given alarm id. |
| * |
| * @param alarmId The alarm id to scroll to. |
| */ |
| private void scrollToAlarm(long alarmId) { |
| int alarmPosition = -1; |
| for (int i = 0; i < mAdapter.getCount(); i++) { |
| long id = mAdapter.getItemId(i); |
| if (id == alarmId) { |
| alarmPosition = i; |
| break; |
| } |
| } |
| |
| if (alarmPosition >= 0) { |
| mAdapter.setNewAlarm(alarmId); |
| mAlarmsList.smoothScrollToPositionFromTop(alarmPosition, 0); |
| } else { |
| // Trying to display a deleted alarm should only happen from a missed notification for |
| // an alarm that has been marked deleted after use. |
| Context context = getActivity().getApplicationContext(); |
| Toast toast = Toast.makeText(context, R.string.missed_alarm_has_been_deleted, |
| Toast.LENGTH_LONG); |
| ToastMaster.setToast(toast); |
| toast.show(); |
| } |
| } |
| |
| @Override |
| public void onLoaderReset(Loader<Cursor> cursorLoader) { |
| mAdapter.swapCursor(null); |
| } |
| |
| private void launchRingTonePicker(Alarm alarm) { |
| mSelectedAlarm = alarm; |
| Uri oldRingtone = Alarm.NO_RINGTONE_URI.equals(alarm.alert) ? null : alarm.alert; |
| final Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); |
| intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, oldRingtone); |
| intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_ALARM); |
| intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, false); |
| startActivityForResult(intent, REQUEST_CODE_RINGTONE); |
| } |
| |
| private void saveRingtoneUri(Intent intent) { |
| Uri uri = intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); |
| if (uri == null) { |
| uri = Alarm.NO_RINGTONE_URI; |
| } |
| mSelectedAlarm.alert = uri; |
| |
| // Save the last selected ringtone as the default for new alarms |
| if (!Alarm.NO_RINGTONE_URI.equals(uri)) { |
| RingtoneManager.setActualDefaultRingtoneUri( |
| getActivity(), RingtoneManager.TYPE_ALARM, uri); |
| } |
| asyncUpdateAlarm(mSelectedAlarm, false); |
| } |
| |
| @Override |
| public void onActivityResult(int requestCode, int resultCode, Intent data) { |
| if (resultCode == Activity.RESULT_OK) { |
| switch (requestCode) { |
| case REQUEST_CODE_RINGTONE: |
| saveRingtoneUri(data); |
| break; |
| default: |
| LogUtils.w("Unhandled request code in onActivityResult: " + requestCode); |
| } |
| } |
| } |
| |
| public class AlarmItemAdapter extends CursorAdapter { |
| private final Context mContext; |
| private final LayoutInflater mFactory; |
| private final String[] mShortWeekDayStrings; |
| private final String[] mLongWeekDayStrings; |
| private final int mColorLit; |
| private final int mColorDim; |
| private final Typeface mRobotoNormal; |
| private final ListView mList; |
| |
| private long mExpandedId; |
| private ItemHolder mExpandedItemHolder; |
| private final HashSet<Long> mRepeatChecked = new HashSet<Long>(); |
| private final HashSet<Long> mSelectedAlarms = new HashSet<Long>(); |
| private Bundle mPreviousDaysOfWeekMap = new Bundle(); |
| |
| private final boolean mHasVibrator; |
| private final int mCollapseExpandHeight; |
| |
| // This determines the order in which it is shown and processed in the UI. |
| private final int[] DAY_ORDER = new int[] { |
| Calendar.SUNDAY, |
| Calendar.MONDAY, |
| Calendar.TUESDAY, |
| Calendar.WEDNESDAY, |
| Calendar.THURSDAY, |
| Calendar.FRIDAY, |
| Calendar.SATURDAY, |
| }; |
| |
| public class ItemHolder { |
| |
| // views for optimization |
| LinearLayout alarmItem; |
| TextTime clock; |
| TextView tomorrowLabel; |
| Switch onoff; |
| TextView daysOfWeek; |
| TextView label; |
| ImageButton delete; |
| View expandArea; |
| View summary; |
| TextView clickableLabel; |
| CheckBox repeat; |
| LinearLayout repeatDays; |
| Button[] dayButtons = new Button[7]; |
| CheckBox vibrate; |
| TextView ringtone; |
| View hairLine; |
| View arrow; |
| View collapseExpandArea; |
| |
| // Other states |
| Alarm alarm; |
| } |
| |
| // Used for scrolling an expanded item in the list to make sure it is fully visible. |
| private long mScrollAlarmId = AlarmClockFragment.INVALID_ID; |
| private final Runnable mScrollRunnable = new Runnable() { |
| @Override |
| public void run() { |
| if (mScrollAlarmId != AlarmClockFragment.INVALID_ID) { |
| View v = getViewById(mScrollAlarmId); |
| if (v != null) { |
| Rect rect = new Rect(v.getLeft(), v.getTop(), v.getRight(), v.getBottom()); |
| mList.requestChildRectangleOnScreen(v, rect, false); |
| } |
| mScrollAlarmId = AlarmClockFragment.INVALID_ID; |
| } |
| } |
| }; |
| |
| public AlarmItemAdapter(Context context, long expandedId, long[] repeatCheckedIds, |
| long[] selectedAlarms, Bundle previousDaysOfWeekMap, ListView list) { |
| super(context, null, 0); |
| mContext = context; |
| mFactory = LayoutInflater.from(context); |
| mList = list; |
| |
| DateFormatSymbols dfs = new DateFormatSymbols(); |
| mShortWeekDayStrings = Utils.getShortWeekdays(); |
| mLongWeekDayStrings = dfs.getWeekdays(); |
| |
| Resources res = mContext.getResources(); |
| mColorLit = res.getColor(R.color.clock_white); |
| mColorDim = res.getColor(R.color.clock_gray); |
| |
| mRobotoNormal = Typeface.create("sans-serif", Typeface.NORMAL); |
| |
| mExpandedId = expandedId; |
| if (repeatCheckedIds != null) { |
| buildHashSetFromArray(repeatCheckedIds, mRepeatChecked); |
| } |
| if (previousDaysOfWeekMap != null) { |
| mPreviousDaysOfWeekMap = previousDaysOfWeekMap; |
| } |
| if (selectedAlarms != null) { |
| buildHashSetFromArray(selectedAlarms, mSelectedAlarms); |
| } |
| |
| mHasVibrator = ((Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE)) |
| .hasVibrator(); |
| |
| mCollapseExpandHeight = (int) res.getDimension(R.dimen.collapse_expand_height); |
| } |
| |
| public void removeSelectedId(int id) { |
| mSelectedAlarms.remove(id); |
| } |
| |
| @Override |
| public View getView(int position, View convertView, ViewGroup parent) { |
| if (!getCursor().moveToPosition(position)) { |
| // May happen if the last alarm was deleted and the cursor refreshed while the |
| // list is updated. |
| LogUtils.v("couldn't move cursor to position " + position); |
| return null; |
| } |
| View v; |
| if (convertView == null) { |
| v = newView(mContext, getCursor(), parent); |
| } else { |
| v = convertView; |
| } |
| bindView(v, mContext, getCursor()); |
| return v; |
| } |
| |
| @Override |
| public View newView(Context context, Cursor cursor, ViewGroup parent) { |
| final View view = mFactory.inflate(R.layout.alarm_time, parent, false); |
| setNewHolder(view); |
| return view; |
| } |
| |
| /** |
| * In addition to changing the data set for the alarm list, swapCursor is now also |
| * responsible for preparing the transition for any added/removed items. |
| */ |
| @Override |
| public synchronized Cursor swapCursor(Cursor cursor) { |
| if (mAddedAlarm != null || mDeletedAlarm != null) { |
| TransitionManager.beginDelayedTransition(mAlarmsList, mAddRemoveTransition); |
| } |
| |
| final Cursor c = super.swapCursor(cursor); |
| |
| mAddedAlarm = null; |
| mDeletedAlarm = null; |
| |
| return c; |
| } |
| |
| private void setNewHolder(View view) { |
| // standard view holder optimization |
| final ItemHolder holder = new ItemHolder(); |
| holder.alarmItem = (LinearLayout) view.findViewById(R.id.alarm_item); |
| holder.tomorrowLabel = (TextView) view.findViewById(R.id.tomorrowLabel); |
| holder.clock = (TextTime) view.findViewById(R.id.digital_clock); |
| holder.onoff = (Switch) view.findViewById(R.id.onoff); |
| holder.onoff.setTypeface(mRobotoNormal); |
| holder.daysOfWeek = (TextView) view.findViewById(R.id.daysOfWeek); |
| holder.label = (TextView) view.findViewById(R.id.label); |
| holder.delete = (ImageButton) view.findViewById(R.id.delete); |
| holder.summary = view.findViewById(R.id.summary); |
| holder.expandArea = view.findViewById(R.id.expand_area); |
| holder.hairLine = view.findViewById(R.id.hairline); |
| holder.arrow = view.findViewById(R.id.arrow); |
| holder.repeat = (CheckBox) view.findViewById(R.id.repeat_onoff); |
| holder.clickableLabel = (TextView) view.findViewById(R.id.edit_label); |
| holder.repeatDays = (LinearLayout) view.findViewById(R.id.repeat_days); |
| holder.collapseExpandArea = view.findViewById(R.id.collapse_expand); |
| |
| // Build button for each day. |
| for (int i = 0; i < 7; i++) { |
| final Button dayButton = (Button) mFactory.inflate( |
| R.layout.day_button, holder.repeatDays, false /* attachToRoot */); |
| dayButton.setText(mShortWeekDayStrings[i]); |
| dayButton.setContentDescription(mLongWeekDayStrings[DAY_ORDER[i]]); |
| holder.repeatDays.addView(dayButton); |
| holder.dayButtons[i] = dayButton; |
| } |
| holder.vibrate = (CheckBox) view.findViewById(R.id.vibrate_onoff); |
| holder.ringtone = (TextView) view.findViewById(R.id.choose_ringtone); |
| |
| view.setTag(holder); |
| } |
| |
| @Override |
| public void bindView(final View view, Context context, final Cursor cursor) { |
| final Alarm alarm = new Alarm(cursor); |
| Object tag = view.getTag(); |
| if (tag == null) { |
| // The view was converted but somehow lost its tag. |
| setNewHolder(view); |
| } |
| final ItemHolder itemHolder = (ItemHolder) tag; |
| itemHolder.alarm = alarm; |
| |
| // We must unset the listener first because this maybe a recycled view so changing the |
| // state would affect the wrong alarm. |
| itemHolder.onoff.setOnCheckedChangeListener(null); |
| itemHolder.onoff.setChecked(alarm.enabled); |
| |
| if (mSelectedAlarms.contains(itemHolder.alarm.id)) { |
| setAlarmItemBackgroundAndElevation(itemHolder.alarmItem, true /* expanded */); |
| setDigitalTimeAlpha(itemHolder, true); |
| itemHolder.onoff.setEnabled(false); |
| } else { |
| itemHolder.onoff.setEnabled(true); |
| setAlarmItemBackgroundAndElevation(itemHolder.alarmItem, false /* expanded */); |
| setDigitalTimeAlpha(itemHolder, itemHolder.onoff.isChecked()); |
| } |
| itemHolder.clock.setFormat( |
| (int)mContext.getResources().getDimension(R.dimen.alarm_label_size)); |
| itemHolder.clock.setTime(alarm.hour, alarm.minutes); |
| itemHolder.clock.setClickable(true); |
| itemHolder.clock.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View view) { |
| mSelectedAlarm = itemHolder.alarm; |
| AlarmUtils.showTimeEditDialog(AlarmClockFragment.this, alarm); |
| expandAlarm(itemHolder, true); |
| itemHolder.alarmItem.post(mScrollRunnable); |
| } |
| }); |
| |
| final CompoundButton.OnCheckedChangeListener onOffListener = |
| new CompoundButton.OnCheckedChangeListener() { |
| @Override |
| public void onCheckedChanged(CompoundButton compoundButton, |
| boolean checked) { |
| if (checked != alarm.enabled) { |
| setDigitalTimeAlpha(itemHolder, checked); |
| alarm.enabled = checked; |
| asyncUpdateAlarm(alarm, alarm.enabled); |
| } |
| } |
| }; |
| |
| if (mRepeatChecked.contains(alarm.id) || itemHolder.alarm.daysOfWeek.isRepeating()) { |
| itemHolder.tomorrowLabel.setVisibility(View.GONE); |
| } else { |
| itemHolder.tomorrowLabel.setVisibility(View.VISIBLE); |
| final Resources resources = getResources(); |
| final String labelText = isTomorrow(alarm) ? |
| resources.getString(R.string.alarm_tomorrow) : |
| resources.getString(R.string.alarm_today); |
| itemHolder.tomorrowLabel.setText(labelText); |
| } |
| itemHolder.onoff.setOnCheckedChangeListener(onOffListener); |
| |
| boolean expanded = isAlarmExpanded(alarm); |
| if (expanded) { |
| mExpandedItemHolder = itemHolder; |
| } |
| itemHolder.expandArea.setVisibility(expanded? View.VISIBLE : View.GONE); |
| itemHolder.delete.setVisibility(expanded ? View.VISIBLE : View.GONE); |
| itemHolder.summary.setVisibility(expanded? View.GONE : View.VISIBLE); |
| itemHolder.hairLine.setVisibility(expanded ? View.GONE : View.VISIBLE); |
| itemHolder.arrow.setRotation(expanded ? ROTATE_180_DEGREE : 0); |
| |
| // Set the repeat text or leave it blank if it does not repeat. |
| final String daysOfWeekStr = |
| alarm.daysOfWeek.toString(AlarmClockFragment.this.getActivity(), false); |
| if (daysOfWeekStr != null && daysOfWeekStr.length() != 0) { |
| itemHolder.daysOfWeek.setText(daysOfWeekStr); |
| itemHolder.daysOfWeek.setContentDescription(alarm.daysOfWeek.toAccessibilityString( |
| AlarmClockFragment.this.getActivity())); |
| itemHolder.daysOfWeek.setVisibility(View.VISIBLE); |
| itemHolder.daysOfWeek.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View view) { |
| expandAlarm(itemHolder, true); |
| itemHolder.alarmItem.post(mScrollRunnable); |
| } |
| }); |
| |
| } else { |
| itemHolder.daysOfWeek.setVisibility(View.GONE); |
| } |
| |
| if (alarm.label != null && alarm.label.length() != 0) { |
| itemHolder.label.setText(alarm.label + " "); |
| itemHolder.label.setVisibility(View.VISIBLE); |
| itemHolder.label.setContentDescription( |
| mContext.getResources().getString(R.string.label_description) + " " |
| + alarm.label); |
| itemHolder.label.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View view) { |
| expandAlarm(itemHolder, true); |
| itemHolder.alarmItem.post(mScrollRunnable); |
| } |
| }); |
| } else { |
| itemHolder.label.setVisibility(View.GONE); |
| } |
| |
| itemHolder.delete.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| mDeletedAlarm = alarm; |
| mRepeatChecked.remove(alarm.id); |
| asyncDeleteAlarm(alarm); |
| } |
| }); |
| |
| if (expanded) { |
| expandAlarm(itemHolder, false); |
| } |
| |
| itemHolder.alarmItem.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View view) { |
| if (isAlarmExpanded(alarm)) { |
| collapseAlarm(itemHolder, true); |
| } else { |
| expandAlarm(itemHolder, true); |
| } |
| } |
| }); |
| } |
| |
| private void setAlarmItemBackgroundAndElevation(LinearLayout layout, boolean expanded) { |
| if (expanded) { |
| layout.setBackgroundColor(getTintedBackgroundColor()); |
| layout.setElevation(ALARM_ELEVATION); |
| } else { |
| layout.setBackgroundResource(R.drawable.alarm_background_normal); |
| layout.setElevation(0); |
| } |
| } |
| |
| private int getTintedBackgroundColor() { |
| final int c = Utils.getCurrentHourColor(); |
| final int red = Color.red(c) + (int) (TINTED_LEVEL * (255 - Color.red(c))); |
| final int green = Color.green(c) + (int) (TINTED_LEVEL * (255 - Color.green(c))); |
| final int blue = Color.blue(c) + (int) (TINTED_LEVEL * (255 - Color.blue(c))); |
| return Color.rgb(red, green, blue); |
| } |
| |
| private boolean isTomorrow(Alarm alarm) { |
| final Calendar now = Calendar.getInstance(); |
| final int alarmHour = alarm.hour; |
| final int currHour = now.get(Calendar.HOUR_OF_DAY); |
| return alarmHour < currHour || |
| (alarmHour == currHour && alarm.minutes < now.get(Calendar.MINUTE)); |
| } |
| |
| private void bindExpandArea(final ItemHolder itemHolder, final Alarm alarm) { |
| // Views in here are not bound until the item is expanded. |
| |
| if (alarm.label != null && alarm.label.length() > 0) { |
| itemHolder.clickableLabel.setText(alarm.label); |
| } else { |
| itemHolder.clickableLabel.setText(R.string.label); |
| } |
| |
| itemHolder.clickableLabel.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View view) { |
| showLabelDialog(alarm); |
| } |
| }); |
| |
| if (mRepeatChecked.contains(alarm.id) || itemHolder.alarm.daysOfWeek.isRepeating()) { |
| itemHolder.repeat.setChecked(true); |
| itemHolder.repeatDays.setVisibility(View.VISIBLE); |
| } else { |
| itemHolder.repeat.setChecked(false); |
| itemHolder.repeatDays.setVisibility(View.GONE); |
| } |
| itemHolder.repeat.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View view) { |
| // Animate the resulting layout changes. |
| TransitionManager.beginDelayedTransition(mList, mRepeatTransition); |
| |
| final boolean checked = ((CheckBox) view).isChecked(); |
| if (checked) { |
| // Show days |
| itemHolder.repeatDays.setVisibility(View.VISIBLE); |
| mRepeatChecked.add(alarm.id); |
| |
| // Set all previously set days |
| // or |
| // Set all days if no previous. |
| final int bitSet = mPreviousDaysOfWeekMap.getInt("" + alarm.id); |
| alarm.daysOfWeek.setBitSet(bitSet); |
| if (!alarm.daysOfWeek.isRepeating()) { |
| alarm.daysOfWeek.setDaysOfWeek(true, DAY_ORDER); |
| } |
| updateDaysOfWeekButtons(itemHolder, alarm.daysOfWeek); |
| } else { |
| // Hide days |
| itemHolder.repeatDays.setVisibility(View.GONE); |
| mRepeatChecked.remove(alarm.id); |
| |
| // Remember the set days in case the user wants it back. |
| final int bitSet = alarm.daysOfWeek.getBitSet(); |
| mPreviousDaysOfWeekMap.putInt("" + alarm.id, bitSet); |
| |
| // Remove all repeat days |
| alarm.daysOfWeek.clearAllDays(); |
| } |
| |
| asyncUpdateAlarm(alarm, false); |
| } |
| }); |
| |
| updateDaysOfWeekButtons(itemHolder, alarm.daysOfWeek); |
| for (int i = 0; i < 7; i++) { |
| final int buttonIndex = i; |
| |
| itemHolder.dayButtons[i].setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View view) { |
| final boolean isActivated = |
| itemHolder.dayButtons[buttonIndex].isActivated(); |
| alarm.daysOfWeek.setDaysOfWeek(!isActivated, DAY_ORDER[buttonIndex]); |
| if (!isActivated) { |
| turnOnDayOfWeek(itemHolder, buttonIndex); |
| } else { |
| turnOffDayOfWeek(itemHolder, buttonIndex); |
| |
| // See if this was the last day, if so, un-check the repeat box. |
| if (!alarm.daysOfWeek.isRepeating()) { |
| // Animate the resulting layout changes. |
| TransitionManager.beginDelayedTransition(mList, mRepeatTransition); |
| |
| itemHolder.repeat.setChecked(false); |
| itemHolder.repeatDays.setVisibility(View.GONE); |
| mRepeatChecked.remove(alarm.id); |
| |
| // Set history to no days, so it will be everyday when repeat is |
| // turned back on |
| mPreviousDaysOfWeekMap.putInt("" + alarm.id, |
| DaysOfWeek.NO_DAYS_SET); |
| } |
| } |
| asyncUpdateAlarm(alarm, false); |
| } |
| }); |
| } |
| |
| if (!mHasVibrator) { |
| itemHolder.vibrate.setVisibility(View.INVISIBLE); |
| } else { |
| itemHolder.vibrate.setVisibility(View.VISIBLE); |
| if (!alarm.vibrate) { |
| itemHolder.vibrate.setChecked(false); |
| } else { |
| itemHolder.vibrate.setChecked(true); |
| } |
| } |
| |
| itemHolder.vibrate.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| final boolean checked = ((CheckBox) v).isChecked(); |
| alarm.vibrate = checked; |
| asyncUpdateAlarm(alarm, false); |
| } |
| }); |
| |
| final String ringtone; |
| if (Alarm.NO_RINGTONE_URI.equals(alarm.alert)) { |
| ringtone = mContext.getResources().getString(R.string.silent_alarm_summary); |
| } else { |
| ringtone = getRingToneTitle(alarm.alert); |
| } |
| itemHolder.ringtone.setText(ringtone); |
| itemHolder.ringtone.setContentDescription( |
| mContext.getResources().getString(R.string.ringtone_description) + " " |
| + ringtone); |
| itemHolder.ringtone.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View view) { |
| launchRingTonePicker(alarm); |
| } |
| }); |
| } |
| |
| // Sets the alpha of the digital time display. This gives a visual effect |
| // for enabled/disabled alarm while leaving the on/off switch more visible |
| private void setDigitalTimeAlpha(ItemHolder holder, boolean enabled) { |
| float alpha = enabled ? 1f : 0.69f; |
| holder.clock.setAlpha(alpha); |
| } |
| |
| private void updateDaysOfWeekButtons(ItemHolder holder, DaysOfWeek daysOfWeek) { |
| HashSet<Integer> setDays = daysOfWeek.getSetDays(); |
| for (int i = 0; i < 7; i++) { |
| if (setDays.contains(DAY_ORDER[i])) { |
| turnOnDayOfWeek(holder, i); |
| } else { |
| turnOffDayOfWeek(holder, i); |
| } |
| } |
| } |
| |
| public void toggleSelectState(View v) { |
| // long press could be on the parent view or one of its childs, so find the parent view |
| v = getTopParent(v); |
| if (v != null) { |
| long id = ((ItemHolder)v.getTag()).alarm.id; |
| if (mSelectedAlarms.contains(id)) { |
| mSelectedAlarms.remove(id); |
| } else { |
| mSelectedAlarms.add(id); |
| } |
| } |
| } |
| |
| private View getTopParent(View v) { |
| while (v != null && v.getId() != R.id.alarm_item) { |
| v = (View) v.getParent(); |
| } |
| return v; |
| } |
| |
| public int getSelectedItemsNum() { |
| return mSelectedAlarms.size(); |
| } |
| |
| private void turnOffDayOfWeek(ItemHolder holder, int dayIndex) { |
| final Button dayButton = holder.dayButtons[dayIndex]; |
| dayButton.setActivated(false); |
| dayButton.setTextColor(getResources().getColor(R.color.clock_white)); |
| } |
| |
| private void turnOnDayOfWeek(ItemHolder holder, int dayIndex) { |
| final Button dayButton = holder.dayButtons[dayIndex]; |
| dayButton.setActivated(true); |
| dayButton.setTextColor(Utils.getCurrentHourColor()); |
| } |
| |
| |
| /** |
| * Does a read-through cache for ringtone titles. |
| * |
| * @param uri The uri of the ringtone. |
| * @return The ringtone title. {@literal null} if no matching ringtone found. |
| */ |
| private String getRingToneTitle(Uri uri) { |
| // Try the cache first |
| String title = mRingtoneTitleCache.getString(uri.toString()); |
| if (title == null) { |
| // This is slow because a media player is created during Ringtone object creation. |
| Ringtone ringTone = RingtoneManager.getRingtone(mContext, uri); |
| title = ringTone.getTitle(mContext); |
| if (title != null) { |
| mRingtoneTitleCache.putString(uri.toString(), title); |
| } |
| } |
| return title; |
| } |
| |
| public void setNewAlarm(long alarmId) { |
| mExpandedId = alarmId; |
| } |
| |
| /** |
| * Expands the alarm for editing. |
| * |
| * @param itemHolder The item holder instance. |
| */ |
| private void expandAlarm(final ItemHolder itemHolder, boolean animate) { |
| // Skip animation later if item is already expanded |
| animate &= mExpandedId != itemHolder.alarm.id; |
| |
| if (mExpandedItemHolder != null |
| && mExpandedItemHolder != itemHolder |
| && mExpandedId != itemHolder.alarm.id) { |
| // Only allow one alarm to expand at a time. |
| collapseAlarm(mExpandedItemHolder, animate); |
| } |
| |
| bindExpandArea(itemHolder, itemHolder.alarm); |
| |
| mExpandedId = itemHolder.alarm.id; |
| mExpandedItemHolder = itemHolder; |
| |
| // Scroll the view to make sure it is fully viewed |
| mScrollAlarmId = itemHolder.alarm.id; |
| |
| // Save the starting height so we can animate from this value. |
| final int startingHeight = itemHolder.alarmItem.getHeight(); |
| |
| // Set the expand area to visible so we can measure the height to animate to. |
| setAlarmItemBackgroundAndElevation(itemHolder.alarmItem, true /* expanded */); |
| itemHolder.expandArea.setVisibility(View.VISIBLE); |
| itemHolder.delete.setVisibility(View.VISIBLE); |
| |
| if (!animate) { |
| // Set the "end" layout and don't do the animation. |
| itemHolder.arrow.setRotation(ROTATE_180_DEGREE); |
| return; |
| } |
| |
| // Add an onPreDrawListener, which gets called after measurement but before the draw. |
| // This way we can check the height we need to animate to before any drawing. |
| // Note the series of events: |
| // * expandArea is set to VISIBLE, which causes a layout pass |
| // * the view is measured, and our onPreDrawListener is called |
| // * we set up the animation using the start and end values. |
| // * the height is set back to the starting point so it can be animated down. |
| // * request another layout pass. |
| // * return false so that onDraw() is not called for the single frame before |
| // the animations have started. |
| final ViewTreeObserver observer = mAlarmsList.getViewTreeObserver(); |
| observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { |
| @Override |
| public boolean onPreDraw() { |
| // We don't want to continue getting called for every listview drawing. |
| if (observer.isAlive()) { |
| observer.removeOnPreDrawListener(this); |
| } |
| // Calculate some values to help with the animation. |
| final int endingHeight = itemHolder.alarmItem.getHeight(); |
| final int distance = endingHeight - startingHeight; |
| final int collapseHeight = itemHolder.collapseExpandArea.getHeight(); |
| |
| // Set the height back to the start state of the animation. |
| itemHolder.alarmItem.getLayoutParams().height = startingHeight; |
| // To allow the expandArea to glide in with the expansion animation, set a |
| // negative top margin, which will animate down to a margin of 0 as the height |
| // is increased. |
| // Note that we need to maintain the bottom margin as a fixed value (instead of |
| // just using a listview, to allow for a flatter hierarchy) to fit the bottom |
| // bar underneath. |
| FrameLayout.LayoutParams expandParams = (FrameLayout.LayoutParams) |
| itemHolder.expandArea.getLayoutParams(); |
| expandParams.setMargins(0, -distance, 0, collapseHeight); |
| itemHolder.alarmItem.requestLayout(); |
| |
| // Set up the animator to animate the expansion. |
| ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f) |
| .setDuration(EXPAND_DURATION); |
| animator.setInterpolator(mExpandInterpolator); |
| animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { |
| @Override |
| public void onAnimationUpdate(ValueAnimator animator) { |
| Float value = (Float) animator.getAnimatedValue(); |
| |
| // For each value from 0 to 1, animate the various parts of the layout. |
| itemHolder.alarmItem.getLayoutParams().height = |
| (int) (value * distance + startingHeight); |
| FrameLayout.LayoutParams expandParams = (FrameLayout.LayoutParams) |
| itemHolder.expandArea.getLayoutParams(); |
| expandParams.setMargins( |
| 0, (int) -((1 - value) * distance), 0, collapseHeight); |
| itemHolder.arrow.setRotation(ROTATE_180_DEGREE * value); |
| itemHolder.summary.setAlpha(1 - value); |
| itemHolder.hairLine.setAlpha(1 - value); |
| |
| itemHolder.alarmItem.requestLayout(); |
| } |
| }); |
| // Set everything to their final values when the animation's done. |
| animator.addListener(new AnimatorListener() { |
| @Override |
| public void onAnimationEnd(Animator animation) { |
| // Set it back to wrap content since we'd explicitly set the height. |
| itemHolder.alarmItem.getLayoutParams().height = |
| LayoutParams.WRAP_CONTENT; |
| itemHolder.arrow.setRotation(ROTATE_180_DEGREE); |
| itemHolder.summary.setVisibility(View.GONE); |
| itemHolder.hairLine.setVisibility(View.GONE); |
| itemHolder.delete.setVisibility(View.VISIBLE); |
| } |
| |
| @Override |
| public void onAnimationCancel(Animator animation) { |
| // TODO we may have to deal with cancelations of the animation. |
| } |
| |
| @Override |
| public void onAnimationRepeat(Animator animation) { } |
| @Override |
| public void onAnimationStart(Animator animation) { } |
| }); |
| animator.start(); |
| |
| // Return false so this draw does not occur to prevent the final frame from |
| // being drawn for the single frame before the animations start. |
| return false; |
| } |
| }); |
| } |
| |
| private boolean isAlarmExpanded(Alarm alarm) { |
| return mExpandedId == alarm.id; |
| } |
| |
| private void collapseAlarm(final ItemHolder itemHolder, boolean animate) { |
| mExpandedId = AlarmClockFragment.INVALID_ID; |
| mExpandedItemHolder = null; |
| |
| // Save the starting height so we can animate from this value. |
| final int startingHeight = itemHolder.alarmItem.getHeight(); |
| |
| // Set the expand area to gone so we can measure the height to animate to. |
| setAlarmItemBackgroundAndElevation(itemHolder.alarmItem, false /* expanded */); |
| itemHolder.expandArea.setVisibility(View.GONE); |
| |
| if (!animate) { |
| // Set the "end" layout and don't do the animation. |
| itemHolder.arrow.setRotation(0); |
| itemHolder.hairLine.setTranslationY(0); |
| return; |
| } |
| |
| // Add an onPreDrawListener, which gets called after measurement but before the draw. |
| // This way we can check the height we need to animate to before any drawing. |
| // Note the series of events: |
| // * expandArea is set to GONE, which causes a layout pass |
| // * the view is measured, and our onPreDrawListener is called |
| // * we set up the animation using the start and end values. |
| // * expandArea is set to VISIBLE again so it can be shown animating. |
| // * request another layout pass. |
| // * return false so that onDraw() is not called for the single frame before |
| // the animations have started. |
| final ViewTreeObserver observer = mAlarmsList.getViewTreeObserver(); |
| observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { |
| @Override |
| public boolean onPreDraw() { |
| if (observer.isAlive()) { |
| observer.removeOnPreDrawListener(this); |
| } |
| |
| // Calculate some values to help with the animation. |
| final int endingHeight = itemHolder.alarmItem.getHeight(); |
| final int distance = endingHeight - startingHeight; |
| |
| // Re-set the visibilities for the start state of the animation. |
| itemHolder.expandArea.setVisibility(View.VISIBLE); |
| itemHolder.delete.setVisibility(View.GONE); |
| itemHolder.summary.setVisibility(View.VISIBLE); |
| itemHolder.hairLine.setVisibility(View.VISIBLE); |
| itemHolder.summary.setAlpha(1); |
| |
| // Set up the animator to animate the expansion. |
| ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f) |
| .setDuration(COLLAPSE_DURATION); |
| animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { |
| @Override |
| public void onAnimationUpdate(ValueAnimator animator) { |
| Float value = (Float) animator.getAnimatedValue(); |
| |
| // For each value from 0 to 1, animate the various parts of the layout. |
| itemHolder.alarmItem.getLayoutParams().height = |
| (int) (value * distance + startingHeight); |
| FrameLayout.LayoutParams expandParams = (FrameLayout.LayoutParams) |
| itemHolder.expandArea.getLayoutParams(); |
| expandParams.setMargins( |
| 0, (int) (value * distance), 0, mCollapseExpandHeight); |
| itemHolder.arrow.setRotation(ROTATE_180_DEGREE * (1 - value)); |
| itemHolder.delete.setAlpha(value); |
| itemHolder.summary.setAlpha(value); |
| itemHolder.hairLine.setAlpha(value); |
| |
| itemHolder.alarmItem.requestLayout(); |
| } |
| }); |
| animator.setInterpolator(mCollapseInterpolator); |
| // Set everything to their final values when the animation's done. |
| animator.addListener(new AnimatorListenerAdapter() { |
| @Override |
| public void onAnimationEnd(Animator animation) { |
| // Set it back to wrap content since we'd explicitly set the height. |
| itemHolder.alarmItem.getLayoutParams().height = |
| LayoutParams.WRAP_CONTENT; |
| |
| FrameLayout.LayoutParams expandParams = (FrameLayout.LayoutParams) |
| itemHolder.expandArea.getLayoutParams(); |
| expandParams.setMargins(0, 0, 0, mCollapseExpandHeight); |
| |
| itemHolder.expandArea.setVisibility(View.GONE); |
| itemHolder.arrow.setRotation(0); |
| } |
| }); |
| animator.start(); |
| |
| return false; |
| } |
| }); |
| } |
| |
| @Override |
| public int getViewTypeCount() { |
| return 1; |
| } |
| |
| private View getViewById(long id) { |
| for (int i = 0; i < mList.getCount(); i++) { |
| View v = mList.getChildAt(i); |
| if (v != null) { |
| ItemHolder h = (ItemHolder)(v.getTag()); |
| if (h != null && h.alarm.id == id) { |
| return v; |
| } |
| } |
| } |
| return null; |
| } |
| |
| public long getExpandedId() { |
| return mExpandedId; |
| } |
| |
| public long[] getSelectedAlarmsArray() { |
| int index = 0; |
| long[] ids = new long[mSelectedAlarms.size()]; |
| for (long id : mSelectedAlarms) { |
| ids[index] = id; |
| index++; |
| } |
| return ids; |
| } |
| |
| public long[] getRepeatArray() { |
| int index = 0; |
| long[] ids = new long[mRepeatChecked.size()]; |
| for (long id : mRepeatChecked) { |
| ids[index] = id; |
| index++; |
| } |
| return ids; |
| } |
| |
| public Bundle getPreviousDaysOfWeekMap() { |
| return mPreviousDaysOfWeekMap; |
| } |
| |
| private void buildHashSetFromArray(long[] ids, HashSet<Long> set) { |
| for (long id : ids) { |
| set.add(id); |
| } |
| } |
| } |
| |
| private void startCreatingAlarm() { |
| // Set the "selected" alarm as null, and we'll create the new one when the timepicker |
| // comes back. |
| mSelectedAlarm = null; |
| AlarmUtils.showTimeEditDialog(this, null); |
| } |
| |
| private static AlarmInstance setupAlarmInstance(Context context, Alarm alarm) { |
| ContentResolver cr = context.getContentResolver(); |
| AlarmInstance newInstance = alarm.createInstanceAfter(Calendar.getInstance()); |
| newInstance = AlarmInstance.addInstance(cr, newInstance); |
| // Register instance to state manager |
| AlarmStateManager.registerInstance(context, newInstance, true); |
| return newInstance; |
| } |
| |
| private void asyncDeleteAlarm(final Alarm alarm) { |
| final Context context = AlarmClockFragment.this.getActivity().getApplicationContext(); |
| final AsyncTask<Void, Void, Void> deleteTask = new AsyncTask<Void, Void, Void>() { |
| @Override |
| protected Void doInBackground(Void... parameters) { |
| // Activity may be closed at this point , make sure data is still valid |
| if (context != null && alarm != null) { |
| ContentResolver cr = context.getContentResolver(); |
| AlarmStateManager.deleteAllInstances(context, alarm.id); |
| Alarm.deleteAlarm(cr, alarm.id); |
| sDeskClockExtensions.deleteAlarm( |
| AlarmClockFragment.this.getActivity().getApplicationContext(), alarm.id); |
| } |
| return null; |
| } |
| }; |
| mUndoShowing = true; |
| deleteTask.execute(); |
| } |
| |
| private void asyncAddAlarm(final Alarm alarm) { |
| final Context context = AlarmClockFragment.this.getActivity().getApplicationContext(); |
| final AsyncTask<Void, Void, AlarmInstance> updateTask = |
| new AsyncTask<Void, Void, AlarmInstance>() { |
| @Override |
| protected AlarmInstance doInBackground(Void... parameters) { |
| if (context != null && alarm != null) { |
| ContentResolver cr = context.getContentResolver(); |
| |
| // Add alarm to db |
| Alarm newAlarm = Alarm.addAlarm(cr, alarm); |
| mScrollToAlarmId = newAlarm.id; |
| |
| // Create and add instance to db |
| if (newAlarm.enabled) { |
| sDeskClockExtensions.addAlarm( |
| AlarmClockFragment.this.getActivity().getApplicationContext(), |
| newAlarm); |
| return setupAlarmInstance(context, newAlarm); |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| protected void onPostExecute(AlarmInstance instance) { |
| if (instance != null) { |
| AlarmUtils.popAlarmSetToast(context, instance.getAlarmTime().getTimeInMillis()); |
| } |
| } |
| }; |
| updateTask.execute(); |
| } |
| |
| private void asyncUpdateAlarm(final Alarm alarm, final boolean popToast) { |
| final Context context = AlarmClockFragment.this.getActivity().getApplicationContext(); |
| final AsyncTask<Void, Void, AlarmInstance> updateTask = |
| new AsyncTask<Void, Void, AlarmInstance>() { |
| @Override |
| protected AlarmInstance doInBackground(Void ... parameters) { |
| ContentResolver cr = context.getContentResolver(); |
| |
| // Dismiss all old instances |
| AlarmStateManager.deleteAllInstances(context, alarm.id); |
| |
| // Update alarm |
| Alarm.updateAlarm(cr, alarm); |
| if (alarm.enabled) { |
| return setupAlarmInstance(context, alarm); |
| } |
| |
| return null; |
| } |
| |
| @Override |
| protected void onPostExecute(AlarmInstance instance) { |
| if (popToast && instance != null) { |
| AlarmUtils.popAlarmSetToast(context, instance.getAlarmTime().getTimeInMillis()); |
| } |
| } |
| }; |
| updateTask.execute(); |
| } |
| |
| @Override |
| public boolean onTouch(View v, MotionEvent event) { |
| hideUndoBar(true, event); |
| return false; |
| } |
| |
| @Override |
| public void onFabClick(View view){ |
| hideUndoBar(true, null); |
| startCreatingAlarm(); |
| } |
| |
| @Override |
| public void setFabAppearance() { |
| final DeskClock activity = (DeskClock) getActivity(); |
| if (mFab == null || activity.getSelectedTab() != DeskClock.ALARM_TAB_INDEX) { |
| return; |
| } |
| mFab.setVisibility(View.VISIBLE); |
| mFab.setImageResource(R.drawable.ic_fab_plus); |
| mFab.setContentDescription(getString(R.string.button_alarms)); |
| } |
| |
| @Override |
| public void setLeftRightButtonAppearance() { |
| final DeskClock activity = (DeskClock) getActivity(); |
| if (mLeftButton == null || mRightButton == null || |
| activity.getSelectedTab() != DeskClock.ALARM_TAB_INDEX) { |
| return; |
| } |
| mLeftButton.setVisibility(View.INVISIBLE); |
| mRightButton.setVisibility(View.INVISIBLE); |
| } |
| } |