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 17 package com.android.deskclock; 18 19 import android.app.Activity; 20 import android.app.Notification; 21 import android.app.NotificationManager; 22 import android.app.PendingIntent; 23 import android.content.BroadcastReceiver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.content.pm.ActivityInfo; 28 import android.content.res.Configuration; 29 import android.os.Bundle; 30 import android.os.Handler; 31 import android.os.Message; 32 import android.preference.PreferenceManager; 33 import android.view.KeyEvent; 34 import android.view.LayoutInflater; 35 import android.view.View; 36 import android.view.Window; 37 import android.view.WindowManager; 38 import android.widget.TextView; 39 import android.widget.Toast; 40 41 import com.android.deskclock.widget.multiwaveview.GlowPadView; 42 43 import 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 */ 50 public 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 mPingEnabled = true; 314 // The activity is locked to the default orientation as a default set in the manifest 315 // Override this settings if the device is docked or config set it differently 316 if (getResources().getBoolean(R.bool.config_rotateAlarmAlert) || mIsDocked) { 317 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); 318 } 319 } 320 321 @Override 322 protected void onPause() { 323 super.onPause(); 324 mPingEnabled = false; 325 } 326 327 @Override 328 public void onDestroy() { 329 super.onDestroy(); 330 if (LOG) Log.v("AlarmAlertFullScreen.onDestroy()"); 331 // No longer care about the alarm being killed. 332 unregisterReceiver(mReceiver); 333 } 334 335 @Override 336 public boolean dispatchKeyEvent(KeyEvent event) { 337 // Do this on key down to handle a few of the system keys. 338 boolean up = event.getAction() == KeyEvent.ACTION_UP; 339 if (LOG) { 340 Log.v("AlarmAlertFullScreen - dispatchKeyEvent " + event.getKeyCode()); 341 } 342 switch (event.getKeyCode()) { 343 // Volume keys and camera keys dismiss the alarm 344 case KeyEvent.KEYCODE_POWER: 345 case KeyEvent.KEYCODE_VOLUME_UP: 346 case KeyEvent.KEYCODE_VOLUME_DOWN: 347 case KeyEvent.KEYCODE_VOLUME_MUTE: 348 case KeyEvent.KEYCODE_CAMERA: 349 case KeyEvent.KEYCODE_FOCUS: 350 if (up) { 351 switch (mVolumeBehavior) { 352 case 1: 353 snooze(); 354 break; 355 356 case 2: 357 dismiss(false, false); 358 break; 359 360 default: 361 break; 362 } 363 } 364 return true; 365 default: 366 break; 367 } 368 return super.dispatchKeyEvent(event); 369 } 370 371 @Override 372 public void onBackPressed() { 373 // Don't allow back to dismiss. This method is overriden by AlarmAlert 374 // so that the dialog is dismissed. 375 if (LOG) { 376 Log.v("AlarmAlertFullScreen - onBackPressed"); 377 } 378 return; 379 } 380 381 382 @Override 383 public void onGrabbed(View v, int handle) { 384 mPingEnabled = false; 385 } 386 387 @Override 388 public void onReleased(View v, int handle) { 389 mPingEnabled = true; 390 triggerPing(); 391 } 392 393 @Override 394 public void onTrigger(View v, int target) { 395 final int resId = mGlowPadView.getResourceIdForTarget(target); 396 switch (resId) { 397 case R.drawable.ic_alarm_alert_snooze: 398 snooze(); 399 break; 400 401 case R.drawable.ic_alarm_alert_dismiss: 402 dismiss(false, false); 403 break; 404 default: 405 // Code should never reach here. 406 Log.e("Trigger detected on unhandled resource. Skipping."); 407 } 408 } 409 410 @Override 411 public void onGrabbedStateChange(View v, int handle) { 412 } 413 414 @Override 415 public void onFinishFinalAnimation() { 416 } 417 } 418