1/* 2 * Copyright (C) 2009 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.Notification; 21import android.app.NotificationManager; 22import android.app.PendingIntent; 23import android.content.BroadcastReceiver; 24import android.content.Context; 25import android.content.Intent; 26import android.content.IntentFilter; 27import android.content.pm.ActivityInfo; 28import android.content.res.Configuration; 29import android.os.Bundle; 30import android.os.Handler; 31import android.os.Message; 32import android.preference.PreferenceManager; 33import android.view.KeyEvent; 34import android.view.LayoutInflater; 35import android.view.View; 36import android.view.Window; 37import android.view.WindowManager; 38import android.widget.TextView; 39import android.widget.Toast; 40 41import com.android.deskclock.widget.multiwaveview.GlowPadView; 42 43import java.util.Calendar; 44 45/** 46 * Alarm Clock alarm alert: pops visible indicator and plays alarm 47 * tone. This activity is the full screen version which shows over the lock 48 * screen with the wallpaper as the background. 49 */ 50public class AlarmAlertFullScreen extends Activity implements GlowPadView.OnTriggerListener { 51 52 private final boolean LOG = true; 53 // These defaults must match the values in res/xml/settings.xml 54 private static final String DEFAULT_SNOOZE = "10"; 55 protected static final String SCREEN_OFF = "screen_off"; 56 57 protected Alarm mAlarm; 58 private int mVolumeBehavior; 59 boolean mFullscreenStyle; 60 private GlowPadView mGlowPadView; 61 private boolean mIsDocked = false; 62 63 // Parameters for the GlowPadView "ping" animation; see triggerPing(). 64 private static final int PING_MESSAGE_WHAT = 101; 65 private static final boolean ENABLE_PING_AUTO_REPEAT = true; 66 private static final long PING_AUTO_REPEAT_DELAY_MSEC = 1200; 67 68 private boolean mPingEnabled = true; 69 70 // Receives the ALARM_KILLED action from the AlarmKlaxon, 71 // and also ALARM_SNOOZE_ACTION / ALARM_DISMISS_ACTION from other applications 72 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 73 @Override 74 public void onReceive(Context context, Intent intent) { 75 String action = intent.getAction(); 76 if (LOG) { 77 Log.v("AlarmAlertFullScreen - onReceive " + action); 78 } 79 if (action.equals(Alarms.ALARM_SNOOZE_ACTION)) { 80 snooze(); 81 } else if (action.equals(Alarms.ALARM_DISMISS_ACTION)) { 82 dismiss(false, false); 83 } else { 84 Alarm alarm = intent.getParcelableExtra(Alarms.ALARM_INTENT_EXTRA); 85 boolean replaced = intent.getBooleanExtra(Alarms.ALARM_REPLACED, false); 86 if (alarm != null && mAlarm.id == alarm.id) { 87 dismiss(true, replaced); 88 } 89 } 90 } 91 }; 92 93 private final Handler mPingHandler = new Handler() { 94 @Override 95 public void handleMessage(Message msg) { 96 switch (msg.what) { 97 case PING_MESSAGE_WHAT: 98 triggerPing(); 99 break; 100 } 101 } 102 }; 103 104 @Override 105 protected void onCreate(Bundle icicle) { 106 super.onCreate(icicle); 107 108 mAlarm = getIntent().getParcelableExtra(Alarms.ALARM_INTENT_EXTRA); 109 110 if (LOG) { 111 Log.v("AlarmAlertFullScreen - onCreate"); 112 if (mAlarm != null) { 113 Log.v("AlarmAlertFullScreen - Alarm Id " + mAlarm.toString()); 114 } 115 } 116 117 // Get the volume/camera button behavior setting 118 final String vol = 119 PreferenceManager.getDefaultSharedPreferences(this) 120 .getString(SettingsActivity.KEY_VOLUME_BEHAVIOR, 121 SettingsActivity.DEFAULT_VOLUME_BEHAVIOR); 122 mVolumeBehavior = Integer.parseInt(vol); 123 124 final Window win = getWindow(); 125 win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED 126 | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); 127 // Turn on the screen unless we are being launched from the AlarmAlert 128 // subclass as a result of the screen turning off. 129 if (!getIntent().getBooleanExtra(SCREEN_OFF, false)) { 130 win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON 131 | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON 132 | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON); 133 } 134 135 updateLayout(); 136 137 // Check the docking status , if the device is docked , do not limit rotation 138 IntentFilter ifilter = new IntentFilter(Intent.ACTION_DOCK_EVENT); 139 Intent dockStatus = registerReceiver(null, ifilter); 140 if (dockStatus != null) { 141 mIsDocked = dockStatus.getIntExtra(Intent.EXTRA_DOCK_STATE, -1) 142 != Intent.EXTRA_DOCK_STATE_UNDOCKED; 143 } 144 145 // Register to get the alarm killed/snooze/dismiss intent. 146 IntentFilter filter = new IntentFilter(Alarms.ALARM_KILLED); 147 filter.addAction(Alarms.ALARM_SNOOZE_ACTION); 148 filter.addAction(Alarms.ALARM_DISMISS_ACTION); 149 registerReceiver(mReceiver, filter); 150 } 151 152 private void setTitle() { 153 final String titleText = mAlarm.getLabelOrDefault(this); 154 155 TextView tv = (TextView) findViewById(R.id.alertTitle); 156 tv.setText(titleText); 157 158 setTitle(titleText); 159 } 160 161 protected int getLayoutResId() { 162 return R.layout.alarm_alert; 163 } 164 165 private void updateLayout() { 166 if (LOG) { 167 Log.v("AlarmAlertFullScreen - updateLayout"); 168 } 169 170 final LayoutInflater inflater = LayoutInflater.from(this); 171 final View view = inflater.inflate(getLayoutResId(), null); 172 view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE); 173 setContentView(view); 174 175 /* Set the title from the passed in alarm */ 176 setTitle(); 177 178 mGlowPadView = (GlowPadView) findViewById(R.id.glow_pad_view); 179 mGlowPadView.setOnTriggerListener(this); 180 triggerPing(); 181 } 182 183 private void triggerPing() { 184 if (mPingEnabled) { 185 mGlowPadView.ping(); 186 187 if (ENABLE_PING_AUTO_REPEAT) { 188 mPingHandler.sendEmptyMessageDelayed(PING_MESSAGE_WHAT, PING_AUTO_REPEAT_DELAY_MSEC); 189 } 190 } 191 } 192 193 // Attempt to snooze this alert. 194 private void snooze() { 195 if (LOG) { 196 Log.v("AlarmAlertFullScreen - snooze"); 197 } 198 199 final String snooze = 200 PreferenceManager.getDefaultSharedPreferences(this) 201 .getString(SettingsActivity.KEY_ALARM_SNOOZE, DEFAULT_SNOOZE); 202 int snoozeMinutes = Integer.parseInt(snooze); 203 204 final long snoozeTime = System.currentTimeMillis() 205 + (1000 * 60 * snoozeMinutes); 206 Alarms.saveSnoozeAlert(AlarmAlertFullScreen.this, mAlarm.id, 207 snoozeTime); 208 209 // Get the display time for the snooze and update the notification. 210 final Calendar c = Calendar.getInstance(); 211 c.setTimeInMillis(snoozeTime); 212 String snoozeTimeStr = Alarms.formatTime(this, c); 213 String label = mAlarm.getLabelOrDefault(this); 214 215 // Notify the user that the alarm has been snoozed. 216 Intent dismissIntent = new Intent(this, AlarmReceiver.class); 217 dismissIntent.setAction(Alarms.CANCEL_SNOOZE); 218 dismissIntent.putExtra(Alarms.ALARM_INTENT_EXTRA, mAlarm); 219 220 Intent openAlarm = new Intent(this, DeskClock.class); 221 openAlarm.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 222 openAlarm.putExtra(Alarms.ALARM_INTENT_EXTRA, mAlarm); 223 openAlarm.putExtra(DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.CLOCK_TAB_INDEX); 224 225 NotificationManager nm = getNotificationManager(); 226 Notification notif = new Notification.Builder(getApplicationContext()) 227 .setContentTitle(label) 228 .setContentText(getResources().getString(R.string.alarm_alert_snooze_until, snoozeTimeStr)) 229 .setSmallIcon(R.drawable.stat_notify_alarm) 230 .setOngoing(true) 231 .setAutoCancel(false) 232 .setPriority(Notification.PRIORITY_MAX) 233 .setWhen(0) 234 .addAction(android.R.drawable.ic_menu_close_clear_cancel, 235 getResources().getString(R.string.alarm_alert_dismiss_text), 236 PendingIntent.getBroadcast(this, mAlarm.id, dismissIntent, 0)) 237 .build(); 238 notif.contentIntent = PendingIntent.getActivity(this, mAlarm.id, openAlarm, 0); 239 nm.notify(mAlarm.id, notif); 240 241 String displayTime = getString(R.string.alarm_alert_snooze_set, 242 snoozeMinutes); 243 // Intentionally log the snooze time for debugging. 244 Log.v(displayTime); 245 246 // Display the snooze minutes in a toast. 247 Toast.makeText(AlarmAlertFullScreen.this, displayTime, 248 Toast.LENGTH_LONG).show(); 249 stopService(new Intent(Alarms.ALARM_ALERT_ACTION)); 250 finish(); 251 } 252 253 private NotificationManager getNotificationManager() { 254 return (NotificationManager) getSystemService(NOTIFICATION_SERVICE); 255 } 256 257 // Dismiss the alarm. 258 private void dismiss(boolean killed, boolean replaced) { 259 if (LOG) { 260 Log.v("AlarmAlertFullScreen - dismiss"); 261 } 262 263 Log.i("Alarm id=" + mAlarm.id + (killed ? (replaced ? " replaced" : " killed") : " dismissed by user")); 264 // The service told us that the alarm has been killed, do not modify 265 // the notification or stop the service. 266 if (!killed) { 267 // Cancel the notification and stop playing the alarm 268 NotificationManager nm = getNotificationManager(); 269 nm.cancel(mAlarm.id); 270 stopService(new Intent(Alarms.ALARM_ALERT_ACTION)); 271 } 272 if (!replaced) { 273 finish(); 274 } 275 } 276 277 /** 278 * this is called when a second alarm is triggered while a 279 * previous alert window is still active. 280 */ 281 @Override 282 protected void onNewIntent(Intent intent) { 283 super.onNewIntent(intent); 284 285 if (LOG) Log.v("AlarmAlert.OnNewIntent()"); 286 287 mAlarm = intent.getParcelableExtra(Alarms.ALARM_INTENT_EXTRA); 288 289 setTitle(); 290 } 291 292 @Override 293 public void onConfigurationChanged(Configuration newConfig) { 294 if (LOG) { 295 Log.v("AlarmAlertFullScreen - onConfigChanged"); 296 } 297 updateLayout(); 298 super.onConfigurationChanged(newConfig); 299 } 300 301 @Override 302 protected void onResume() { 303 super.onResume(); 304 if (LOG) { 305 Log.v("AlarmAlertFullScreen - onResume"); 306 } 307 // If the alarm was deleted at some point, disable snooze. 308 if (Alarms.getAlarm(getContentResolver(), mAlarm.id) == null) { 309 mGlowPadView.setTargetResources(R.array.dismiss_drawables); 310 mGlowPadView.setTargetDescriptionsResourceId(R.array.dismiss_descriptions); 311 mGlowPadView.setDirectionDescriptionsResourceId(R.array.dismiss_direction_descriptions); 312 } 313 // The activity is locked to the default orientation as a default set in the manifest 314 // Override this settings if the device is docked or config set it differently 315 if (getResources().getBoolean(R.bool.config_rotateAlarmAlert) || mIsDocked) { 316 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); 317 } 318 } 319 320 @Override 321 public void onDestroy() { 322 super.onDestroy(); 323 if (LOG) Log.v("AlarmAlertFullScreen.onDestroy()"); 324 // No longer care about the alarm being killed. 325 unregisterReceiver(mReceiver); 326 } 327 328 @Override 329 public boolean dispatchKeyEvent(KeyEvent event) { 330 // Do this on key down to handle a few of the system keys. 331 boolean up = event.getAction() == KeyEvent.ACTION_UP; 332 if (LOG) { 333 Log.v("AlarmAlertFullScreen - dispatchKeyEvent " + event.getKeyCode()); 334 } 335 switch (event.getKeyCode()) { 336 // Volume keys and camera keys dismiss the alarm 337 case KeyEvent.KEYCODE_POWER: 338 case KeyEvent.KEYCODE_VOLUME_UP: 339 case KeyEvent.KEYCODE_VOLUME_DOWN: 340 case KeyEvent.KEYCODE_VOLUME_MUTE: 341 case KeyEvent.KEYCODE_CAMERA: 342 case KeyEvent.KEYCODE_FOCUS: 343 if (up) { 344 switch (mVolumeBehavior) { 345 case 1: 346 snooze(); 347 break; 348 349 case 2: 350 dismiss(false, false); 351 break; 352 353 default: 354 break; 355 } 356 } 357 return true; 358 default: 359 break; 360 } 361 return super.dispatchKeyEvent(event); 362 } 363 364 @Override 365 public void onBackPressed() { 366 // Don't allow back to dismiss. This method is overriden by AlarmAlert 367 // so that the dialog is dismissed. 368 if (LOG) { 369 Log.v("AlarmAlertFullScreen - onBackPressed"); 370 } 371 return; 372 } 373 374 375 @Override 376 public void onGrabbed(View v, int handle) { 377 mPingEnabled = false; 378 } 379 380 @Override 381 public void onReleased(View v, int handle) { 382 mPingEnabled = true; 383 triggerPing(); 384 } 385 386 @Override 387 public void onTrigger(View v, int target) { 388 final int resId = mGlowPadView.getResourceIdForTarget(target); 389 switch (resId) { 390 case R.drawable.ic_alarm_alert_snooze: 391 snooze(); 392 break; 393 394 case R.drawable.ic_alarm_alert_dismiss: 395 dismiss(false, false); 396 break; 397 default: 398 // Code should never reach here. 399 Log.e("Trigger detected on unhandled resource. Skipping."); 400 } 401 } 402 403 @Override 404 public void onGrabbedStateChange(View v, int handle) { 405 } 406 407 @Override 408 public void onFinishFinalAnimation() { 409 } 410} 411