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 17package com.android.deskclock; 18 19import android.app.Activity; 20import android.app.LoaderManager; 21import android.content.Intent; 22import android.content.Loader; 23import android.database.Cursor; 24import android.media.RingtoneManager; 25import android.net.Uri; 26import android.os.Bundle; 27import android.support.design.widget.Snackbar; 28import android.support.v7.widget.LinearLayoutManager; 29import android.support.v7.widget.RecyclerView; 30import android.text.format.DateFormat; 31import android.view.LayoutInflater; 32import android.view.View; 33import android.view.ViewGroup; 34 35import com.android.deskclock.alarms.AlarmTimeClickHandler; 36import com.android.deskclock.alarms.AlarmUpdateHandler; 37import com.android.deskclock.alarms.ScrollHandler; 38import com.android.deskclock.alarms.TimePickerCompat; 39import com.android.deskclock.alarms.dataadapter.AlarmTimeAdapter; 40import com.android.deskclock.data.DataModel; 41import com.android.deskclock.provider.Alarm; 42import com.android.deskclock.widget.EmptyViewController; 43import com.android.deskclock.widget.toast.SnackbarManager; 44import com.android.deskclock.widget.toast.ToastManager; 45 46/** 47 * A fragment that displays a list of alarm time and allows interaction with them. 48 */ 49public 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