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 private static final String DEFAULT_VOLUME_BEHAVIOR = "2"; 56 protected static final String SCREEN_OFF = "screen_off"; 57 58 protected Alarm mAlarm; 59 private int mVolumeBehavior; 60 boolean mFullscreenStyle; 61 private GlowPadView mGlowPadView; 62 private boolean mIsDocked = false; 63 64 // Parameters for the GlowPadView "ping" animation; see triggerPing(). 65 private static final int PING_MESSAGE_WHAT = 101; 66 private static final boolean ENABLE_PING_AUTO_REPEAT = true; 67 private static final long PING_AUTO_REPEAT_DELAY_MSEC = 1200; 68 69 private boolean mPingEnabled = true; 70 71 // Receives the ALARM_KILLED action from the AlarmKlaxon, 72 // and also ALARM_SNOOZE_ACTION / ALARM_DISMISS_ACTION from other applications 73 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 74 @Override 75 public void onReceive(Context context, Intent intent) { 76 String action = intent.getAction(); 77 if (LOG) { 78 Log.v("AlarmAlertFullScreen - onReceive " + action); 79 } 80 if (action.equals(Alarms.ALARM_SNOOZE_ACTION)) { 81 snooze(); 82 } else if (action.equals(Alarms.ALARM_DISMISS_ACTION)) { 83 dismiss(false); 84 } else { 85 Alarm alarm = intent.getParcelableExtra(Alarms.ALARM_INTENT_EXTRA); 86 if (alarm != null && mAlarm.id == alarm.id) { 87 dismiss(true); 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 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) { 259 if (LOG) { 260 Log.v("AlarmAlertFullScreen - dismiss"); 261 } 262 263 Log.i(killed ? "Alarm killed" : "Alarm 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 finish(); 273 } 274 275 /** 276 * this is called when a second alarm is triggered while a 277 * previous alert window is still active. 278 */ 279 @Override 280 protected void onNewIntent(Intent intent) { 281 super.onNewIntent(intent); 282 283 if (LOG) Log.v("AlarmAlert.OnNewIntent()"); 284 285 mAlarm = intent.getParcelableExtra(Alarms.ALARM_INTENT_EXTRA); 286 287 setTitle(); 288 } 289 290 @Override 291 public void onConfigurationChanged(Configuration newConfig) { 292 if (LOG) { 293 Log.v("AlarmAlertFullScreen - onConfigChanged"); 294 } 295 updateLayout(); 296 super.onConfigurationChanged(newConfig); 297 } 298 299 @Override 300 protected void onResume() { 301 super.onResume(); 302 if (LOG) { 303 Log.v("AlarmAlertFullScreen - onResume"); 304 } 305 // If the alarm was deleted at some point, disable snooze. 306 if (Alarms.getAlarm(getContentResolver(), mAlarm.id) == null) { 307 mGlowPadView.setTargetResources(R.array.dismiss_drawables); 308 mGlowPadView.setTargetDescriptionsResourceId(R.array.dismiss_descriptions); 309 mGlowPadView.setDirectionDescriptionsResourceId(R.array.dismiss_direction_descriptions); 310 } 311 // The activity is locked to the default orientation as a default set in the manifest 312 // Override this settings if the device is docked or config set it differently 313 if (getResources().getBoolean(R.bool.config_rotateAlarmAlert) || mIsDocked) { 314 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); 315 } 316 } 317 318 @Override 319 public void onDestroy() { 320 super.onDestroy(); 321 if (LOG) Log.v("AlarmAlertFullScreen.onDestroy()"); 322 // No longer care about the alarm being killed. 323 unregisterReceiver(mReceiver); 324 } 325 326 @Override 327 public boolean dispatchKeyEvent(KeyEvent event) { 328 // Do this on key down to handle a few of the system keys. 329 boolean up = event.getAction() == KeyEvent.ACTION_UP; 330 if (LOG) { 331 Log.v("AlarmAlertFullScreen - dispatchKeyEvent " + event.getKeyCode()); 332 } 333 switch (event.getKeyCode()) { 334 // Volume keys and camera keys dismiss the alarm 335 case KeyEvent.KEYCODE_POWER: 336 case KeyEvent.KEYCODE_VOLUME_UP: 337 case KeyEvent.KEYCODE_VOLUME_DOWN: 338 case KeyEvent.KEYCODE_VOLUME_MUTE: 339 case KeyEvent.KEYCODE_CAMERA: 340 case KeyEvent.KEYCODE_FOCUS: 341 if (up) { 342 switch (mVolumeBehavior) { 343 case 1: 344 snooze(); 345 break; 346 347 case 2: 348 dismiss(false); 349 break; 350 351 default: 352 break; 353 } 354 } 355 return true; 356 default: 357 break; 358 } 359 return super.dispatchKeyEvent(event); 360 } 361 362 @Override 363 public void onBackPressed() { 364 // Don't allow back to dismiss. This method is overriden by AlarmAlert 365 // so that the dialog is dismissed. 366 if (LOG) { 367 Log.v("AlarmAlertFullScreen - onBackPressed"); 368 } 369 return; 370 } 371 372 373 @Override 374 public void onGrabbed(View v, int handle) { 375 mPingEnabled = false; 376 } 377 378 @Override 379 public void onReleased(View v, int handle) { 380 mPingEnabled = true; 381 triggerPing(); 382 } 383 384 @Override 385 public void onTrigger(View v, int target) { 386 final int resId = mGlowPadView.getResourceIdForTarget(target); 387 switch (resId) { 388 case R.drawable.ic_alarm_alert_snooze: 389 snooze(); 390 break; 391 392 case R.drawable.ic_alarm_alert_dismiss: 393 dismiss(false); 394 break; 395 default: 396 // Code should never reach here. 397 Log.e("Trigger detected on unhandled resource. Skipping."); 398 } 399 } 400 401 @Override 402 public void onGrabbedStateChange(View v, int handle) { 403 } 404 405 @Override 406 public void onFinishFinalAnimation() { 407 } 408 } 409