| /* |
| * 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.app.ActionBar; |
| import android.app.Activity; |
| import android.app.AlertDialog; |
| import android.app.Fragment; |
| import android.app.FragmentTransaction; |
| import android.app.LoaderManager; |
| import android.content.Context; |
| import android.content.DialogInterface; |
| import android.content.Intent; |
| import android.content.Loader; |
| import android.content.res.Resources; |
| import android.database.Cursor; |
| 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.view.ActionMode; |
| import android.view.ActionMode.Callback; |
| import android.view.LayoutInflater; |
| import android.view.Menu; |
| import android.view.MenuItem; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.view.View.OnLongClickListener; |
| import android.view.ViewGroup; |
| import android.widget.CheckBox; |
| import android.widget.CompoundButton; |
| import android.widget.CursorAdapter; |
| import android.widget.LinearLayout; |
| import android.widget.ListView; |
| import android.widget.Switch; |
| import android.widget.TextView; |
| import android.widget.ToggleButton; |
| |
| import com.android.deskclock.widget.ActionableToastBar; |
| import com.android.deskclock.widget.swipeablelistview.SwipeableListView; |
| |
| import java.text.DateFormatSymbols; |
| import java.util.Calendar; |
| import java.util.HashSet; |
| |
| /** |
| * AlarmClock application. |
| */ |
| public class AlarmClock extends Activity implements LoaderManager.LoaderCallbacks<Cursor>, |
| AlarmTimePickerDialogFragment.AlarmTimePickerDialogHandler, |
| LabelDialogFragment.AlarmLabelDialogHandler, |
| OnLongClickListener, Callback, DialogInterface.OnClickListener, |
| DialogInterface.OnCancelListener { |
| |
| private static final String KEY_EXPANDED_IDS = "expandedIds"; |
| 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 String KEY_DELETE_CONFIRMATION = "deleteConfirmation"; |
| |
| private static final int REQUEST_CODE_RINGTONE = 1; |
| |
| private SwipeableListView mAlarmsList; |
| private AlarmItemAdapter mAdapter; |
| private Bundle mRingtoneTitleCache; // Key: ringtone uri, value: ringtone title |
| private ActionableToastBar mUndoBar; |
| private ActionMode mActionMode; |
| |
| private Alarm mSelectedAlarm; |
| private int mScrollToAlarmId = -1; |
| private boolean mInDeleteConfirmation = false; |
| |
| // This flag relies on the activity having a "standard" launchMode and a new instance of this |
| // activity being created when launched. |
| private boolean mFirstLoad = true; |
| |
| // Saved states for undo |
| private Alarm mDeletedAlarm; |
| private boolean mUndoShowing = false; |
| |
| @Override |
| protected void onCreate(Bundle savedState) { |
| super.onCreate(savedState); |
| initialize(savedState); |
| updateLayout(); |
| getLoaderManager().initLoader(0, null, this); |
| } |
| |
| private void initialize(Bundle savedState) { |
| setContentView(R.layout.alarm_clock); |
| int[] expandedIds = null; |
| int[] repeatCheckedIds = null; |
| int[] selectedAlarms = null; |
| Bundle previousDayMap = null; |
| if (savedState != null) { |
| expandedIds = savedState.getIntArray(KEY_EXPANDED_IDS); |
| repeatCheckedIds = savedState.getIntArray(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.getIntArray(KEY_SELECTED_ALARMS); |
| previousDayMap = savedState.getBundle(KEY_PREVIOUS_DAY_MAP); |
| mSelectedAlarm = savedState.getParcelable(KEY_SELECTED_ALARM); |
| mInDeleteConfirmation = savedState.getBoolean(KEY_DELETE_CONFIRMATION, false); |
| } |
| |
| mAlarmsList = (SwipeableListView) findViewById(R.id.alarms_list); |
| mAdapter = new AlarmItemAdapter( |
| this, expandedIds, repeatCheckedIds, selectedAlarms, previousDayMap, mAlarmsList); |
| mAdapter.setLongClickListener(this); |
| |
| if (mRingtoneTitleCache == null) { |
| mRingtoneTitleCache = new Bundle(); |
| } |
| |
| mAlarmsList.setAdapter(mAdapter); |
| mAlarmsList.setVerticalScrollBarEnabled(true); |
| mAlarmsList.enableSwipe(true); |
| mAlarmsList.setOnCreateContextMenuListener(this); |
| mAlarmsList.setOnItemSwipeListener(new SwipeableListView.OnItemSwipeListener() { |
| @Override |
| public void onSwipe(View view) { |
| final AlarmItemAdapter.ItemHolder itemHolder = |
| (AlarmItemAdapter.ItemHolder) view.getTag(); |
| mAdapter.removeSelectedId(itemHolder.alarm.id); |
| updateActionMode(); |
| asyncDeleteAlarm(itemHolder.alarm); |
| } |
| }); |
| mAlarmsList.setOnTouchListener(new View.OnTouchListener() { |
| @Override |
| public boolean onTouch(View view, MotionEvent event) { |
| hideUndoBar(true, event); |
| return false; |
| } |
| }); |
| |
| mUndoBar = (ActionableToastBar) findViewById(R.id.undo_bar); |
| |
| if (mUndoShowing) { |
| mUndoBar.show(new ActionableToastBar.ActionClickedListener() { |
| @Override |
| public void onActionClicked() { |
| asyncAddAlarm(mDeletedAlarm, false); |
| mDeletedAlarm = null; |
| mUndoShowing = false; |
| } |
| }, 0, getResources().getString(R.string.alarm_deleted), true, R.string.alarm_undo, |
| true); |
| } |
| |
| // Show action mode if needed |
| int selectedNum = mAdapter.getSelectedItemsNum(); |
| if (selectedNum > 0) { |
| mActionMode = startActionMode(this); |
| setActionModeTitle(selectedNum); |
| } |
| |
| } |
| |
| @Override |
| public void onResume() { |
| super.onResume(); |
| if (mInDeleteConfirmation) { |
| showConfirmationDialog(); |
| } |
| } |
| private void hideUndoBar(boolean animate, MotionEvent event) { |
| if (mUndoBar != null) { |
| if (event != null && mUndoBar.isEventInToastBar(event)) { |
| // Avoid touches inside the undo bar. |
| return; |
| } |
| mUndoBar.hide(animate); |
| } |
| mDeletedAlarm = null; |
| mUndoShowing = false; |
| } |
| |
| @Override |
| protected void onSaveInstanceState(Bundle outState) { |
| super.onSaveInstanceState(outState); |
| outState.putIntArray(KEY_EXPANDED_IDS, mAdapter.getExpandedArray()); |
| outState.putIntArray(KEY_REPEAT_CHECKED_IDS, mAdapter.getRepeatArray()); |
| outState.putIntArray(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); |
| outState.putBoolean(KEY_DELETE_CONFIRMATION, mInDeleteConfirmation); |
| } |
| |
| private void updateLayout() { |
| final ActionBar actionBar = getActionBar(); |
| if (actionBar != null) { |
| actionBar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP, ActionBar.DISPLAY_HOME_AS_UP); |
| } |
| } |
| |
| @Override |
| protected void onDestroy() { |
| super.onDestroy(); |
| ToastMaster.cancelToast(); |
| } |
| |
| @Override |
| public boolean onOptionsItemSelected(MenuItem item) { |
| hideUndoBar(true, null); |
| switch (item.getItemId()) { |
| case R.id.menu_item_settings: |
| startActivity(new Intent(this, SettingsActivity.class)); |
| return true; |
| case R.id.menu_item_add_alarm: |
| asyncAddAlarm(); |
| return true; |
| case R.id.menu_item_delete_alarm: |
| if (mAdapter != null) { |
| mAdapter.deleteSelectedAlarms(); |
| } |
| return true; |
| case android.R.id.home: |
| Intent intent = new Intent(this, DeskClock.class); |
| intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); |
| startActivity(intent); |
| return true; |
| default: |
| |
| break; |
| } |
| return super.onOptionsItemSelected(item); |
| } |
| |
| @Override |
| public boolean onCreateOptionsMenu(Menu menu) { |
| getMenuInflater().inflate(R.menu.alarm_list_menu, menu); |
| MenuItem help = menu.findItem(R.id.menu_item_help); |
| if (help != null) { |
| Utils.prepareHelpMenuItem(this, help); |
| } |
| return super.onCreateOptionsMenu(menu); |
| } |
| |
| @Override |
| protected void onRestart() { |
| super.onRestart(); |
| // 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. |
| if (mUndoBar != null) { |
| hideUndoBar(false, null); |
| } |
| } |
| |
| // Callback used by AlarmTimePickerDialogFragment |
| @Override |
| public void onDialogTimeSet(Alarm alarm, int hourOfDay, int minute) { |
| alarm.hour = hourOfDay; |
| alarm.minutes = minute; |
| alarm.enabled = true; |
| mScrollToAlarmId = alarm.id; |
| asyncUpdateAlarm(alarm, true); |
| } |
| |
| 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); |
| newFragment.show(ft, "label_dialog"); |
| } |
| |
| // Callback used by AlarmLabelDialogFragment. |
| @Override |
| public void onDialogLabelSet(Alarm alarm, String label) { |
| alarm.label = label; |
| asyncUpdateAlarm(alarm, false); |
| } |
| |
| @Override |
| public Loader<Cursor> onCreateLoader(int id, Bundle args) { |
| return Alarms.getAlarmsCursorLoader(this); |
| } |
| |
| @Override |
| public void onLoadFinished(Loader<Cursor> cursorLoader, final Cursor data) { |
| mAdapter.swapCursor(data); |
| gotoAlarmIfSpecified(); |
| } |
| |
| /** If an alarm was passed in via intent and goes to that particular alarm in the list. */ |
| private void gotoAlarmIfSpecified() { |
| final Intent intent = getIntent(); |
| if (mFirstLoad && intent != null) { |
| final Alarm alarm = (Alarm) intent.getParcelableExtra(Alarms.ALARM_INTENT_EXTRA); |
| if (alarm != null) { |
| scrollToAlarm(alarm.id); |
| } |
| } else if (mScrollToAlarmId != -1) { |
| scrollToAlarm(mScrollToAlarmId); |
| mScrollToAlarmId = -1; |
| } |
| mFirstLoad = false; |
| } |
| |
| /** |
| * Scroll to alarm with given alarm id. |
| * |
| * @param alarmId The alarm id to scroll to. |
| */ |
| private void scrollToAlarm(int alarmId) { |
| for (int i = 0; i < mAdapter.getCount(); i++) { |
| long id = mAdapter.getItemId(i); |
| if (id == alarmId) { |
| mAdapter.setNewAlarm(alarmId); |
| mAlarmsList.smoothScrollToPositionFromTop(i, 0); |
| |
| final int firstPositionId = mAlarmsList.getFirstVisiblePosition(); |
| final int childId = i - firstPositionId; |
| |
| final View view = mAlarmsList.getChildAt(childId); |
| mAdapter.getView(i, view, mAlarmsList); |
| break; |
| } |
| } |
| } |
| |
| @Override |
| public void onLoaderReset(Loader<Cursor> cursorLoader) { |
| mAdapter.swapCursor(null); |
| } |
| |
| private void launchRingTonePicker(Alarm alarm) { |
| mSelectedAlarm = alarm; |
| final Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); |
| intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, alarm.alert); |
| 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) { |
| final Uri uri = intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); |
| mSelectedAlarm.alert = uri; |
| // Save the last selected ringtone as the default for new alarms |
| RingtoneManager.setActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM, uri); |
| asyncUpdateAlarm(mSelectedAlarm, false); |
| } |
| |
| @Override |
| protected void onActivityResult(int requestCode, int resultCode, Intent data) { |
| if (resultCode == RESULT_OK) { |
| switch (requestCode) { |
| case REQUEST_CODE_RINGTONE: |
| saveRingtoneUri(data); |
| break; |
| default: |
| Log.w("Unhandled request code in onActivityResult: " + requestCode); |
| } |
| } |
| } |
| |
| /*** |
| * On long click, mark/unmark the selected view and activate/deactivate action mode |
| */ |
| @Override |
| public boolean onLongClick(View v) { |
| mAdapter.toggleSelectState(v); |
| mAdapter.notifyDataSetChanged(); |
| updateActionMode(); |
| return false; |
| } |
| |
| /*** |
| * Activate/update/close action mode according to the number of selected views. |
| */ |
| private void updateActionMode() { |
| int selectedNum = mAdapter.getSelectedItemsNum(); |
| if (mActionMode == null && selectedNum > 0) { |
| // Start the action mode |
| mActionMode = startActionMode(this); |
| setActionModeTitle(selectedNum); |
| } else if (mActionMode != null) { |
| if (selectedNum > 0) { |
| // Update the number of selected items in the title |
| setActionModeTitle(selectedNum); |
| } else { |
| // No selected items. close the action mode |
| mActionMode.finish(); |
| mActionMode = null; |
| } |
| } |
| } |
| |
| /*** |
| * Display the number of selected items on the action bar in action mode |
| * @param items - number of selected items |
| */ |
| private void setActionModeTitle(int items) { |
| mActionMode.setTitle(String.format(getString(R.string.alarms_selected), items)); |
| } |
| |
| 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 int mBackgroundColorSelected; |
| private final int mBackgroundColor; |
| private final Typeface mRobotoNormal; |
| private final Typeface mRobotoBold; |
| private OnLongClickListener mLongClickListener; |
| private final ListView mList; |
| |
| private final HashSet<Integer> mExpanded = new HashSet<Integer>(); |
| private final HashSet<Integer> mRepeatChecked = new HashSet<Integer>(); |
| private final HashSet<Integer> mSelectedAlarms = new HashSet<Integer>(); |
| private Bundle mPreviousDaysOfWeekMap = new Bundle(); |
| |
| private final boolean mHasVibrator; |
| |
| // 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; |
| DigitalClock clock; |
| Switch onoff; |
| TextView daysOfWeek; |
| TextView label; |
| View expandArea; |
| View infoArea; |
| TextView clickableLabel; |
| CheckBox repeat; |
| LinearLayout repeatDays; |
| ViewGroup[] dayButtonParents = new ViewGroup[7]; |
| ToggleButton[] dayButtons = new ToggleButton[7]; |
| CheckBox vibrate; |
| ViewGroup collapse; |
| TextView ringtone; |
| View hairLine; |
| |
| // Other states |
| Alarm alarm; |
| } |
| |
| // Used for scrolling an expanded item in the list to make sure it is fully visible. |
| private int mScrollAlarmId = -1; |
| private final Runnable mScrollRunnable = new Runnable() { |
| @Override |
| public void run() { |
| if (mScrollAlarmId != -1) { |
| 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 = -1; |
| } |
| } |
| }; |
| |
| public AlarmItemAdapter(Context context, int[] expandedIds, int[] repeatCheckedIds, |
| int[] selectedAlarms, Bundle previousDaysOfWeekMap, ListView list) { |
| super(context, null, 0); |
| mContext = context; |
| mFactory = LayoutInflater.from(context); |
| mList = list; |
| |
| DateFormatSymbols dfs = new DateFormatSymbols(); |
| mShortWeekDayStrings = dfs.getShortWeekdays(); |
| mLongWeekDayStrings = dfs.getWeekdays(); |
| |
| Resources res = mContext.getResources(); |
| mColorLit = res.getColor(R.color.clock_white); |
| mColorDim = res.getColor(R.color.clock_gray); |
| mBackgroundColorSelected = res.getColor(R.color.alarm_selected_color); |
| mBackgroundColor = res.getColor(R.color.alarm_whiteish); |
| |
| |
| mRobotoBold = Typeface.create("sans-serif-condensed", Typeface.BOLD); |
| mRobotoNormal = Typeface.create("sans-serif-condensed", Typeface.NORMAL); |
| |
| if (expandedIds != null) { |
| buildHashSetFromArray(expandedIds, mExpanded); |
| } |
| 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(); |
| } |
| |
| public void removeSelectedId(int id) { |
| mSelectedAlarms.remove(id); |
| } |
| |
| public void setLongClickListener(OnLongClickListener l) { |
| mLongClickListener = l; |
| } |
| |
| @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. |
| Log.v("couldn't move cursor to position " + position); |
| return null; |
| } |
| View v; |
| if (convertView == null) { |
| v = newView(mContext, getCursor(), parent); |
| } else { |
| // Do a translation check to test for animation. Change this to something more |
| // reliable and robust in the future. |
| if (convertView.getTranslationX() != 0 || convertView.getTranslationY() != 0) { |
| // view was animated, reset |
| 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); |
| |
| // standard view holder optimization |
| final ItemHolder holder = new ItemHolder(); |
| holder.alarmItem = (LinearLayout) view.findViewById(R.id.alarm_item); |
| holder.clock = (DigitalClock) view.findViewById(R.id.digital_clock); |
| holder.clock.setLive(false); |
| 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.expandArea = view.findViewById(R.id.expand_area); |
| holder.infoArea = view.findViewById(R.id.info_area); |
| holder.repeat = (CheckBox) view.findViewById(R.id.repeat_onoff); |
| holder.clickableLabel = (TextView) view.findViewById(R.id.edit_label); |
| holder.hairLine = view.findViewById(R.id.hairline); |
| holder.repeatDays = (LinearLayout) view.findViewById(R.id.repeat_days); |
| |
| // Build button for each day. |
| for (int i = 0; i < 7; i++) { |
| final ViewGroup viewgroup = (ViewGroup) mFactory.inflate(R.layout.day_button, |
| holder.repeatDays, false); |
| final ToggleButton button = (ToggleButton) viewgroup.getChildAt(0); |
| final int dayToShowIndex = DAY_ORDER[i]; |
| button.setText(mShortWeekDayStrings[dayToShowIndex]); |
| button.setTextOn(mShortWeekDayStrings[dayToShowIndex]); |
| button.setTextOff(mShortWeekDayStrings[dayToShowIndex]); |
| button.setContentDescription(mLongWeekDayStrings[dayToShowIndex]); |
| holder.repeatDays.addView(viewgroup); |
| holder.dayButtons[i] = button; |
| holder.dayButtonParents[i] = viewgroup; |
| } |
| holder.vibrate = (CheckBox) view.findViewById(R.id.vibrate_onoff); |
| holder.collapse = (ViewGroup) view.findViewById(R.id.collapse); |
| holder.ringtone = (TextView) view.findViewById(R.id.choose_ringtone); |
| |
| view.setTag(holder); |
| return view; |
| } |
| |
| @Override |
| public void bindView(View view, Context context, final Cursor cursor) { |
| final Alarm alarm = new Alarm(cursor); |
| final ItemHolder itemHolder = (ItemHolder) view.getTag(); |
| 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)) { |
| itemHolder.alarmItem.setBackgroundColor(mBackgroundColorSelected); |
| setItemAlpha(itemHolder, true); |
| itemHolder.onoff.setEnabled(false); |
| } else { |
| itemHolder.onoff.setEnabled(true); |
| itemHolder.alarmItem.setBackgroundColor(mBackgroundColor); |
| setItemAlpha(itemHolder, itemHolder.onoff.isChecked()); |
| } |
| final CompoundButton.OnCheckedChangeListener onOffListener = |
| new CompoundButton.OnCheckedChangeListener() { |
| @Override |
| public void onCheckedChanged(CompoundButton compoundButton, |
| boolean checked) { |
| //When action mode is on - simulate long click |
| if (doLongClick(compoundButton)) { |
| return; |
| } |
| if (checked != alarm.enabled) { |
| setItemAlpha(itemHolder, checked); |
| alarm.enabled = checked; |
| asyncUpdateAlarm(alarm, alarm.enabled); |
| } |
| } |
| }; |
| |
| itemHolder.onoff.setOnCheckedChangeListener(onOffListener); |
| itemHolder.onoff.setOnLongClickListener(mLongClickListener); |
| |
| itemHolder.clock.updateTime(alarm.hour, alarm.minutes); |
| itemHolder.clock.setClickable(true); |
| itemHolder.clock.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View view) { |
| //When action mode is on - simulate long click |
| if (doLongClick(view)) { |
| return; |
| } |
| AlarmUtils.showTimeEditDialog(AlarmClock.this.getFragmentManager(), alarm); |
| expandAlarm(itemHolder); |
| itemHolder.alarmItem.post(mScrollRunnable); |
| } |
| }); |
| itemHolder.clock.setOnLongClickListener(mLongClickListener); |
| |
| itemHolder.expandArea.setVisibility(isAlarmExpanded(alarm) ? View.VISIBLE : View.GONE); |
| itemHolder.expandArea.setOnLongClickListener(mLongClickListener); |
| itemHolder.infoArea.setVisibility(!isAlarmExpanded(alarm) ? View.VISIBLE : View.GONE); |
| itemHolder.infoArea.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View view) { |
| //When action mode is on - simulate long click |
| if (doLongClick(view)) { |
| return; |
| } |
| expandAlarm(itemHolder); |
| itemHolder.alarmItem.post(mScrollRunnable); |
| } |
| }); |
| itemHolder.infoArea.setOnLongClickListener(mLongClickListener); |
| |
| String colons = ""; |
| // Set the repeat text or leave it blank if it does not repeat. |
| final String daysOfWeekStr = alarm.daysOfWeek.toString(AlarmClock.this, false); |
| if (daysOfWeekStr != null && daysOfWeekStr.length() != 0) { |
| itemHolder.daysOfWeek.setText(daysOfWeekStr); |
| itemHolder.daysOfWeek.setContentDescription( |
| alarm.daysOfWeek.toAccessibilityString(AlarmClock.this)); |
| itemHolder.daysOfWeek.setVisibility(View.VISIBLE); |
| colons = ": "; |
| itemHolder.daysOfWeek.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View view) { |
| //When action mode is on - simulate long click |
| if (doLongClick(view)) { |
| return; |
| } |
| expandAlarm(itemHolder); |
| itemHolder.alarmItem.post(mScrollRunnable); |
| } |
| }); |
| itemHolder.daysOfWeek.setOnLongClickListener(mLongClickListener); |
| |
| } else { |
| itemHolder.daysOfWeek.setVisibility(View.GONE); |
| } |
| |
| if (alarm.label != null && alarm.label.length() != 0) { |
| itemHolder.label.setText(alarm.label + colons); |
| 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) { |
| //When action mode is on - simulate long click |
| if (doLongClick(view)) { |
| return; |
| } |
| expandAlarm(itemHolder); |
| itemHolder.alarmItem.post(mScrollRunnable); |
| } |
| }); |
| itemHolder.label.setOnLongClickListener(mLongClickListener); |
| } else { |
| itemHolder.label.setVisibility(View.GONE); |
| } |
| |
| if (isAlarmExpanded(alarm)) { |
| expandAlarm(itemHolder); |
| } |
| view.setOnLongClickListener(mLongClickListener); |
| view.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View view) { |
| //When action mode is on - simulate long click |
| doLongClick(view); |
| } |
| }); |
| } |
| |
| 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); |
| itemHolder.clickableLabel.setTextColor(mColorLit); |
| } else { |
| itemHolder.clickableLabel.setText(R.string.label); |
| itemHolder.clickableLabel.setTextColor(mColorDim); |
| } |
| itemHolder.clickableLabel.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View view) { |
| //When action mode is on - simulate long click |
| if (doLongClick(view)) { |
| return; |
| } |
| showLabelDialog(alarm); |
| } |
| }); |
| itemHolder.clickableLabel.setOnLongClickListener(mLongClickListener); |
| |
| if (mRepeatChecked.contains(alarm.id) || itemHolder.alarm.daysOfWeek.isRepeatSet()) { |
| itemHolder.repeat.setChecked(true); |
| itemHolder.repeatDays.setVisibility(View.VISIBLE); |
| itemHolder.repeatDays.setOnLongClickListener(mLongClickListener); |
| } else { |
| itemHolder.repeat.setChecked(false); |
| itemHolder.repeatDays.setVisibility(View.GONE); |
| } |
| itemHolder.repeat.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View view) { |
| //When action mode is on - simulate long click |
| if (doLongClick(view)) { |
| return; |
| } |
| 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 daysOfWeekCoded = mPreviousDaysOfWeekMap.getInt("" + alarm.id); |
| if (daysOfWeekCoded == 0) { |
| for (int day : DAY_ORDER) { |
| alarm.daysOfWeek.setDayOfWeek(day, true); |
| } |
| } else { |
| alarm.daysOfWeek.set(new Alarm.DaysOfWeek(daysOfWeekCoded)); |
| } |
| updateDaysOfWeekButtons(itemHolder, alarm.daysOfWeek); |
| } else { |
| itemHolder.repeatDays.setVisibility(View.GONE); |
| mRepeatChecked.remove(alarm.id); |
| |
| // Remember the set days in case the user wants it back. |
| final int daysOfWeekCoded = alarm.daysOfWeek.getCoded(); |
| mPreviousDaysOfWeekMap.putInt("" + alarm.id, daysOfWeekCoded); |
| |
| // Remove all repeat days |
| alarm.daysOfWeek.set(new Alarm.DaysOfWeek(0)); |
| } |
| asyncUpdateAlarm(alarm, false); |
| } |
| }); |
| itemHolder.repeat.setOnLongClickListener(mLongClickListener); |
| |
| updateDaysOfWeekButtons(itemHolder, alarm.daysOfWeek); |
| for (int i = 0; i < 7; i++) { |
| final int buttonIndex = i; |
| |
| itemHolder.dayButtonParents[i].setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View view) { |
| //When action mode is on - simulate long click |
| if (doLongClick(view)) { |
| return; |
| } |
| itemHolder.dayButtons[buttonIndex].toggle(); |
| final boolean checked = itemHolder.dayButtons[buttonIndex].isChecked(); |
| int day = DAY_ORDER[buttonIndex]; |
| alarm.daysOfWeek.setDayOfWeek(day, checked); |
| if (checked) { |
| turnOnDayOfWeek(itemHolder, buttonIndex); |
| } else { |
| turnOffDayOfWeek(itemHolder, buttonIndex); |
| |
| // See if this was the last day, if so, un-check the repeat box. |
| if (alarm.daysOfWeek.getCoded() == 0) { |
| itemHolder.repeatDays.setVisibility(View.GONE); |
| itemHolder.repeat.setTextColor(mColorDim); |
| mRepeatChecked.remove(alarm.id); |
| |
| // Remember the set days in case the user wants it back. |
| mPreviousDaysOfWeekMap.putInt("" + alarm.id, 0); |
| } |
| } |
| asyncUpdateAlarm(alarm, false); |
| } |
| }); |
| } |
| |
| |
| if (!mHasVibrator) { |
| itemHolder.vibrate.setVisibility(View.INVISIBLE); |
| } else { |
| itemHolder.vibrate.setVisibility(View.VISIBLE); |
| if (!alarm.vibrate) { |
| itemHolder.vibrate.setChecked(false); |
| itemHolder.vibrate.setTextColor(mColorDim); |
| } else { |
| itemHolder.vibrate.setChecked(true); |
| itemHolder.vibrate.setTextColor(mColorLit); |
| } |
| itemHolder.vibrate.setOnLongClickListener(mLongClickListener); |
| } |
| |
| itemHolder.vibrate.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| final boolean checked = ((CheckBox) v).isChecked(); |
| //When action mode is on - simulate long click |
| if (doLongClick(v)) { |
| return; |
| } |
| if (checked) { |
| itemHolder.vibrate.setTextColor(mColorLit); |
| } else { |
| itemHolder.vibrate.setTextColor(mColorDim); |
| } |
| alarm.vibrate = checked; |
| asyncUpdateAlarm(alarm, false); |
| } |
| }); |
| |
| itemHolder.collapse.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| //When action mode is on - simulate long click |
| if (doLongClick(v)) { |
| return; |
| } |
| itemHolder.expandArea.setVisibility(LinearLayout.GONE); |
| itemHolder.infoArea.setVisibility(View.VISIBLE); |
| collapseAlarm(alarm); |
| } |
| }); |
| itemHolder.collapse.setOnLongClickListener(mLongClickListener); |
| |
| final String ringtone; |
| if (alarm.alert == null) { |
| 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) { |
| //When action mode is on - simulate long click |
| if (doLongClick(view)) { |
| return; |
| } |
| launchRingTonePicker(alarm); |
| } |
| }); |
| itemHolder.ringtone.setOnLongClickListener(mLongClickListener); |
| } |
| |
| // Sets the alpha of the item except the on/off switch. This gives a visual effect |
| // for enabled/disabled alarm while leaving the on/off switch more visible |
| private void setItemAlpha(ItemHolder holder, boolean enabled) { |
| float alpha = enabled ? 1f : 0.5f; |
| holder.clock.setAlpha(alpha); |
| holder.infoArea.setAlpha(alpha); |
| holder.expandArea.setAlpha(alpha); |
| holder.hairLine.setAlpha(alpha); |
| } |
| |
| private void updateDaysOfWeekButtons(ItemHolder holder, Alarm.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); |
| } |
| } |
| } |
| |
| /*** |
| * Simulate a long click to override clicks on view when ActionMode is on |
| * Returns true if handled a long click, false if not |
| */ |
| private boolean doLongClick(View v) { |
| if (mActionMode == null) { |
| return false; |
| } |
| v = getTopParent(v); |
| if (v != null) { |
| toggleSelectState(v); |
| notifyDataSetChanged(); |
| updateActionMode(); |
| } |
| return true; |
| } |
| |
| 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) { |
| int 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) { |
| holder.dayButtons[dayIndex].setChecked(false); |
| holder.dayButtons[dayIndex].setTextColor(mColorDim); |
| holder.dayButtons[dayIndex].setTypeface(mRobotoNormal); |
| } |
| |
| private void turnOnDayOfWeek(ItemHolder holder, int dayIndex) { |
| holder.dayButtons[dayIndex].setChecked(true); |
| holder.dayButtons[dayIndex].setTextColor(mColorLit); |
| holder.dayButtons[dayIndex].setTypeface(mRobotoBold); |
| } |
| |
| |
| /** |
| * 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(int alarmId) { |
| mExpanded.add(alarmId); |
| } |
| |
| /** |
| * Expands the alarm for editing. |
| * |
| * @param itemHolder The item holder instance. |
| */ |
| private void expandAlarm(ItemHolder itemHolder) { |
| itemHolder.expandArea.setVisibility(View.VISIBLE); |
| itemHolder.expandArea.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View view) { |
| //When action mode is on - simulate long click |
| doLongClick(view); |
| } |
| }); |
| itemHolder.infoArea.setVisibility(View.GONE); |
| |
| mExpanded.add(itemHolder.alarm.id); |
| bindExpandArea(itemHolder, itemHolder.alarm); |
| // Scroll the view to make sure it is fully viewed |
| mScrollAlarmId = itemHolder.alarm.id; |
| } |
| |
| private boolean isAlarmExpanded(Alarm alarm) { |
| return mExpanded.contains(alarm.id); |
| } |
| |
| private void collapseAlarm(Alarm alarm) { |
| mExpanded.remove(alarm.id); |
| } |
| |
| @Override |
| public int getViewTypeCount() { |
| return 1; |
| } |
| |
| private View getViewById(int 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 int[] getExpandedArray() { |
| final int[] ids = new int[mExpanded.size()]; |
| int index = 0; |
| for (int id : mExpanded) { |
| ids[index] = id; |
| index++; |
| } |
| return ids; |
| } |
| |
| public int[] getSelectedAlarmsArray() { |
| final int[] ids = new int[mSelectedAlarms.size()]; |
| int index = 0; |
| for (int id : mSelectedAlarms) { |
| ids[index] = id; |
| index++; |
| } |
| return ids; |
| } |
| |
| public int[] getRepeatArray() { |
| final int[] ids = new int[mRepeatChecked.size()]; |
| int index = 0; |
| for (int id : mRepeatChecked) { |
| ids[index] = id; |
| index++; |
| } |
| return ids; |
| } |
| |
| public Bundle getPreviousDaysOfWeekMap() { |
| return mPreviousDaysOfWeekMap; |
| } |
| |
| private void buildHashSetFromArray(int[] ids, HashSet<Integer> set) { |
| for (int id : ids) { |
| set.add(id); |
| } |
| } |
| |
| public void deleteSelectedAlarms() { |
| Integer ids [] = new Integer[mSelectedAlarms.size()]; |
| int index = 0; |
| for (int id : mSelectedAlarms) { |
| ids[index] = id; |
| index ++; |
| } |
| asyncDeleteAlarm(ids); |
| clearSelectedAlarms(); |
| } |
| |
| public void clearSelectedAlarms() { |
| mSelectedAlarms.clear(); |
| notifyDataSetChanged(); |
| } |
| } |
| |
| private void asyncAddAlarm() { |
| Alarm a = new Alarm(); |
| a.alert = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM); |
| asyncAddAlarm(a, true); |
| } |
| |
| private void asyncDeleteAlarm(final Integer [] alarmIds) { |
| final AsyncTask<Integer, Void, Void> deleteTask = new AsyncTask<Integer, Void, Void>() { |
| @Override |
| protected Void doInBackground(Integer... ids) { |
| for (final int id : ids) { |
| Alarms.deleteAlarm(AlarmClock.this, id); |
| } |
| return null; |
| } |
| }; |
| deleteTask.execute(alarmIds); |
| } |
| |
| private void asyncDeleteAlarm(final Alarm alarm) { |
| final AsyncTask<Alarm, Void, Void> deleteTask = new AsyncTask<Alarm, Void, Void>() { |
| |
| @Override |
| protected Void doInBackground(Alarm... alarms) { |
| for (final Alarm alarm : alarms) { |
| Alarms.deleteAlarm(AlarmClock.this, alarm.id); |
| } |
| return null; |
| } |
| }; |
| mDeletedAlarm = alarm; |
| mUndoShowing = true; |
| deleteTask.execute(alarm); |
| mUndoBar.show(new ActionableToastBar.ActionClickedListener() { |
| @Override |
| public void onActionClicked() { |
| asyncAddAlarm(alarm, false); |
| mDeletedAlarm = null; |
| mUndoShowing = false; |
| } |
| }, 0, getResources().getString(R.string.alarm_deleted), true, R.string.alarm_undo, true); |
| } |
| |
| private void asyncAddAlarm(final Alarm alarm, final boolean showTimePicker) { |
| final AsyncTask<Void, Void, Void> updateTask = new AsyncTask<Void, Void, Void>() { |
| @Override |
| protected Void doInBackground(Void... aVoid) { |
| Alarms.addAlarm(AlarmClock.this, alarm); |
| return null; |
| } |
| |
| @Override |
| protected void onPostExecute(Void aVoid) { |
| if (alarm.enabled) { |
| popToast(alarm); |
| } |
| mAdapter.setNewAlarm(alarm.id); |
| scrollToAlarm(alarm.id); |
| |
| // We need to refresh the first view item because bindView may have been called |
| // before setNewAlarm took effect. In that case, the newly created alarm will not be |
| // expanded. |
| View view = mAlarmsList.getChildAt(0); |
| mAdapter.getView(0, view, mAlarmsList); |
| if (showTimePicker) { |
| AlarmUtils.showTimeEditDialog(AlarmClock.this.getFragmentManager(), alarm); |
| } |
| } |
| }; |
| updateTask.execute(); |
| } |
| |
| private void asyncUpdateAlarm(final Alarm alarm, final boolean popToast) { |
| final AsyncTask<Alarm, Void, Void> updateTask = new AsyncTask<Alarm, Void, Void>() { |
| @Override |
| protected Void doInBackground(Alarm... alarms) { |
| for (final Alarm alarm : alarms) { |
| Alarms.setAlarm(AlarmClock.this, alarm); |
| } |
| return null; |
| } |
| |
| @Override |
| protected void onPostExecute(Void aVoid) { |
| if (popToast) { |
| popToast(alarm); |
| } |
| } |
| }; |
| updateTask.execute(alarm); |
| } |
| |
| private void popToast(Alarm alarm) { |
| AlarmUtils.popAlarmSetToast(this, alarm.hour, alarm.minutes, alarm.daysOfWeek); |
| } |
| |
| /*** |
| * Support for action mode when the user long presses an item in the alarms list |
| */ |
| |
| @Override |
| public boolean onActionItemClicked(ActionMode mode, MenuItem item) { |
| switch (item.getItemId()) { |
| // Delete selected items and close CAB. |
| case R.id.menu_item_delete_alarm: |
| showConfirmationDialog(); |
| break; |
| default: |
| break; |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean onCreateActionMode(ActionMode mode, Menu menu) { |
| getMenuInflater().inflate(R.menu.alarm_cab_menu, menu); |
| return true; |
| } |
| |
| @Override |
| public void onDestroyActionMode(ActionMode arg0) { |
| if(mAdapter != null) { |
| mAdapter.clearSelectedAlarms(); |
| } |
| mActionMode = null; |
| } |
| |
| @Override |
| public boolean onPrepareActionMode(ActionMode arg0, Menu arg1) { |
| return false; |
| } |
| |
| /*** |
| * Handle the delete alarms confirmation dialog |
| */ |
| |
| private void showConfirmationDialog() { |
| AlertDialog.Builder b = new AlertDialog.Builder(this); |
| Resources res = getResources(); |
| String msg = String.format(res.getQuantityText(R.plurals.alarm_delete_confirmation, |
| mAdapter.getSelectedItemsNum()).toString()); |
| b.setCancelable(true).setMessage(msg) |
| .setOnCancelListener(this) |
| .setNegativeButton(res.getString(android.R.string.cancel), this) |
| .setPositiveButton(res.getString(android.R.string.ok), this).show(); |
| mInDeleteConfirmation = true; |
| } |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| if (which == -1) { |
| if (mAdapter != null) { |
| mAdapter.deleteSelectedAlarms(); |
| mActionMode.finish(); |
| } |
| } |
| dialog.dismiss(); |
| mInDeleteConfirmation = false; |
| } |
| |
| @Override |
| public void onCancel(DialogInterface dialog) { |
| mInDeleteConfirmation = false; |
| } |
| } |