Home | History | Annotate | Download | only in deskclock
      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