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        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