Home | History | Annotate | Download | only in deskclock
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.deskclock;
     18 
     19 import android.app.Activity;
     20 import android.app.LoaderManager;
     21 import android.content.Intent;
     22 import android.content.Loader;
     23 import android.database.Cursor;
     24 import android.media.RingtoneManager;
     25 import android.net.Uri;
     26 import android.os.Bundle;
     27 import android.support.design.widget.Snackbar;
     28 import android.support.v7.widget.LinearLayoutManager;
     29 import android.support.v7.widget.RecyclerView;
     30 import android.text.format.DateFormat;
     31 import android.view.LayoutInflater;
     32 import android.view.View;
     33 import android.view.ViewGroup;
     34 
     35 import com.android.deskclock.alarms.AlarmTimeClickHandler;
     36 import com.android.deskclock.alarms.AlarmUpdateHandler;
     37 import com.android.deskclock.alarms.ScrollHandler;
     38 import com.android.deskclock.alarms.TimePickerCompat;
     39 import com.android.deskclock.alarms.dataadapter.AlarmTimeAdapter;
     40 import com.android.deskclock.data.DataModel;
     41 import com.android.deskclock.provider.Alarm;
     42 import com.android.deskclock.widget.EmptyViewController;
     43 import com.android.deskclock.widget.toast.SnackbarManager;
     44 import com.android.deskclock.widget.toast.ToastManager;
     45 
     46 /**
     47  * A fragment that displays a list of alarm time and allows interaction with them.
     48  */
     49 public final class AlarmClockFragment extends DeskClockFragment implements
     50         LoaderManager.LoaderCallbacks<Cursor>, ScrollHandler, TimePickerCompat.OnTimeSetListener {
     51 
     52     // This extra is used when receiving an intent to create an alarm, but no alarm details
     53     // have been passed in, so the alarm page should start the process of creating a new alarm.
     54     public static final String ALARM_CREATE_NEW_INTENT_EXTRA = "deskclock.create.new";
     55 
     56     // This extra is used when receiving an intent to scroll to specific alarm. If alarm
     57     // can not be found, and toast message will pop up that the alarm has be deleted.
     58     public static final String SCROLL_TO_ALARM_INTENT_EXTRA = "deskclock.scroll.to.alarm";
     59 
     60     // Views
     61     private ViewGroup mMainLayout;
     62     private RecyclerView mRecyclerView;
     63 
     64     // Data
     65     private long mScrollToAlarmId = Alarm.INVALID_ID;
     66     private Loader mCursorLoader = null;
     67 
     68     // Controllers
     69     private AlarmTimeAdapter mAlarmTimeAdapter;
     70     private AlarmUpdateHandler mAlarmUpdateHandler;
     71     private EmptyViewController mEmptyViewController;
     72     private AlarmTimeClickHandler mAlarmTimeClickHandler;
     73     private LinearLayoutManager mLayoutManager;
     74 
     75     @Override
     76     public void processTimeSet(int hourOfDay, int minute) {
     77         mAlarmTimeClickHandler.processTimeSet(hourOfDay, minute);
     78     }
     79 
     80     @Override
     81     public void onCreate(Bundle savedState) {
     82         super.onCreate(savedState);
     83         mCursorLoader = getLoaderManager().initLoader(0, null, this);
     84     }
     85 
     86     @Override
     87     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
     88         // Inflate the layout for this fragment
     89         final View v = inflater.inflate(R.layout.alarm_clock, container, false);
     90 
     91         mRecyclerView = (RecyclerView) v.findViewById(R.id.alarms_recycler_view);
     92         mLayoutManager = new LinearLayoutManager(getActivity());
     93         mRecyclerView.setLayoutManager(mLayoutManager);
     94         mMainLayout = (ViewGroup) v.findViewById(R.id.main);
     95         mAlarmUpdateHandler = new AlarmUpdateHandler(getActivity(), this, mMainLayout);
     96         mEmptyViewController = new EmptyViewController(mMainLayout, mRecyclerView,
     97                 v.findViewById(R.id.alarms_empty_view));
     98         mAlarmTimeClickHandler = new AlarmTimeClickHandler(this, savedState, mAlarmUpdateHandler,
     99                 this);
    100         mAlarmTimeAdapter = new AlarmTimeAdapter(getActivity(), savedState,
    101                 mAlarmTimeClickHandler, this);
    102         mRecyclerView.setAdapter(mAlarmTimeAdapter);
    103 
    104         return v;
    105     }
    106 
    107     @Override
    108     public void onResume() {
    109         super.onResume();
    110 
    111         final DeskClock activity = (DeskClock) getActivity();
    112         if (activity.getSelectedTab() == DeskClock.ALARM_TAB_INDEX) {
    113             setFabAppearance();
    114             setLeftRightButtonAppearance();
    115         }
    116 
    117         // Check if another app asked us to create a blank new alarm.
    118         final Intent intent = getActivity().getIntent();
    119         if (intent.hasExtra(ALARM_CREATE_NEW_INTENT_EXTRA)) {
    120             if (intent.getBooleanExtra(ALARM_CREATE_NEW_INTENT_EXTRA, false)) {
    121                 // An external app asked us to create a blank alarm.
    122                 startCreatingAlarm();
    123             }
    124 
    125             // Remove the CREATE_NEW extra now that we've processed it.
    126             intent.removeExtra(ALARM_CREATE_NEW_INTENT_EXTRA);
    127         } else if (intent.hasExtra(SCROLL_TO_ALARM_INTENT_EXTRA)) {
    128             long alarmId = intent.getLongExtra(SCROLL_TO_ALARM_INTENT_EXTRA, Alarm.INVALID_ID);
    129             if (alarmId != Alarm.INVALID_ID) {
    130                 setSmoothScrollStableId(alarmId);
    131                 if (mCursorLoader != null && mCursorLoader.isStarted()) {
    132                     // We need to force a reload here to make sure we have the latest view
    133                     // of the data to scroll to.
    134                     mCursorLoader.forceLoad();
    135                 }
    136             }
    137 
    138             // Remove the SCROLL_TO_ALARM extra now that we've processed it.
    139             intent.removeExtra(SCROLL_TO_ALARM_INTENT_EXTRA);
    140         }
    141     }
    142 
    143     @Override
    144     public void smoothScrollTo(int position) {
    145         mLayoutManager.scrollToPositionWithOffset(position, 20);
    146     }
    147 
    148     @Override
    149     public void onSaveInstanceState(Bundle outState) {
    150         super.onSaveInstanceState(outState);
    151         mAlarmTimeAdapter.saveInstance(outState);
    152         mAlarmTimeClickHandler.saveInstance(outState);
    153     }
    154 
    155     @Override
    156     public void onDestroy() {
    157         super.onDestroy();
    158         ToastManager.cancelToast();
    159     }
    160 
    161     @Override
    162     public void onPause() {
    163         super.onPause();
    164         // When the user places the app in the background by pressing "home",
    165         // dismiss the toast bar. However, since there is no way to determine if
    166         // home was pressed, just dismiss any existing toast bar when restarting
    167         // the app.
    168         mAlarmUpdateHandler.hideUndoBar();
    169     }
    170 
    171     public void setLabel(Alarm alarm, String label) {
    172         alarm.label = label;
    173         mAlarmUpdateHandler.asyncUpdateAlarm(alarm, false, true);
    174     }
    175 
    176     @Override
    177     public Loader<Cursor> onCreateLoader(int id, Bundle args) {
    178         return Alarm.getAlarmsCursorLoader(getActivity());
    179     }
    180 
    181     @Override
    182     public void onLoadFinished(Loader<Cursor> cursorLoader, final Cursor data) {
    183         mEmptyViewController.setEmpty(data.getCount() == 0);
    184         mAlarmTimeAdapter.swapCursor(data);
    185         if (mScrollToAlarmId != Alarm.INVALID_ID) {
    186             scrollToAlarm(mScrollToAlarmId);
    187             setSmoothScrollStableId(Alarm.INVALID_ID);
    188         }
    189     }
    190 
    191     /**
    192      * Scroll to alarm with given alarm id.
    193      *
    194      * @param alarmId The alarm id to scroll to.
    195      */
    196     private void scrollToAlarm(long alarmId) {
    197         final int alarmCount = mAlarmTimeAdapter.getItemCount();
    198         int alarmPosition = -1;
    199         for (int i = 0; i < alarmCount; i++) {
    200             long id = mAlarmTimeAdapter.getItemId(i);
    201             if (id == alarmId) {
    202                 alarmPosition = i;
    203                 break;
    204             }
    205         }
    206 
    207         if (alarmPosition >= 0) {
    208             mAlarmTimeAdapter.expand(alarmPosition);
    209         } else {
    210             // Trying to display a deleted alarm should only happen from a missed notification for
    211             // an alarm that has been marked deleted after use.
    212             SnackbarManager.show(Snackbar.make(mMainLayout, R.string
    213                     .missed_alarm_has_been_deleted, Snackbar.LENGTH_LONG));
    214         }
    215     }
    216 
    217     @Override
    218     public void onLoaderReset(Loader<Cursor> cursorLoader) {
    219         mAlarmTimeAdapter.swapCursor(null);
    220     }
    221 
    222     @Override
    223     public void onActivityResult(int requestCode, int resultCode, Intent data) {
    224         if (resultCode != Activity.RESULT_OK) {
    225             return;
    226         }
    227 
    228         switch (requestCode) {
    229             case R.id.request_code_ringtone:
    230                 // Extract the selected ringtone uri.
    231                 Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
    232                 if (uri == null) {
    233                     uri = Alarm.NO_RINGTONE_URI;
    234                 }
    235 
    236                 // Update the default ringtone for future new alarms.
    237                 DataModel.getDataModel().setDefaultAlarmRingtoneUri(uri);
    238 
    239                 // Set the ringtone uri on the alarm.
    240                 final Alarm alarm = mAlarmTimeClickHandler.getSelectedAlarm();
    241                 if (alarm == null) {
    242                     LogUtils.e("Could not get selected alarm to set ringtone");
    243                     return;
    244                 }
    245                 alarm.alert = uri;
    246 
    247                 // Save the change to alarm.
    248                 mAlarmUpdateHandler.asyncUpdateAlarm(alarm, false /* popToast */,
    249                         true /* minorUpdate */);
    250                 break;
    251             default:
    252                 LogUtils.w("Unhandled request code in onActivityResult: " + requestCode);
    253         }
    254     }
    255 
    256     @Override
    257     public void setSmoothScrollStableId(long stableId) {
    258         mScrollToAlarmId = stableId;
    259     }
    260 
    261     @Override
    262     public void onFabClick(View view) {
    263         mAlarmUpdateHandler.hideUndoBar();
    264         startCreatingAlarm();
    265     }
    266 
    267     @Override
    268     public void setFabAppearance() {
    269         if (mFab == null || getDeskClock().getSelectedTab() != DeskClock.ALARM_TAB_INDEX) {
    270             return;
    271         }
    272         mFab.setVisibility(View.VISIBLE);
    273         mFab.setImageResource(R.drawable.ic_add_white_24dp);
    274         mFab.setContentDescription(getString(R.string.button_alarms));
    275     }
    276 
    277     @Override
    278     public void setLeftRightButtonAppearance() {
    279         if (mLeftButton == null || mRightButton == null ||
    280                 getDeskClock().getSelectedTab() != DeskClock.ALARM_TAB_INDEX) {
    281             return;
    282         }
    283         mLeftButton.setVisibility(View.INVISIBLE);
    284         mRightButton.setVisibility(View.INVISIBLE);
    285     }
    286 
    287     private void startCreatingAlarm() {
    288         mAlarmTimeClickHandler.clearSelectedAlarm();
    289         TimePickerCompat.showTimeEditDialog(this, null /* alarm */,
    290                 DateFormat.is24HourFormat(getActivity()));
    291     }
    292 }
    293