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