diff options
author | Mao Jinlong <c_jmao@codeaurora.org> | 2015-10-28 15:18:36 +0800 |
---|---|---|
committer | Steve Kondik <steve@cyngn.com> | 2015-11-30 19:32:13 -0800 |
commit | 81e6b54aa2dbba531b4a17f512c44d883db63dd4 (patch) | |
tree | 1c3c321df899ae40d08a113d5de963814a0f4d4a | |
parent | 78cc04628091f8d7818ca5e1b2f883efe8731918 (diff) | |
download | android_packages_apps_DeskClock-81e6b54aa2dbba531b4a17f512c44d883db63dd4.tar.gz android_packages_apps_DeskClock-81e6b54aa2dbba531b4a17f512c44d883db63dd4.tar.bz2 android_packages_apps_DeskClock-81e6b54aa2dbba531b4a17f512c44d883db63dd4.zip |
DeskClock: add support for power off alarm
When it is alarm boot, it will trigger power off alarm alert view at
the very beginning of the phone boot phase.
During power off alarm alert view:
- keyguard is dismissed, will restore later
- user can choose to continue power on the device or not
Change-Id: Id5bce67793cef694712b0591be68ef6882be6693
-rw-r--r-- | Android.mk | 3 | ||||
-rw-r--r-- | AndroidManifest.xml | 10 | ||||
-rw-r--r-- | res/values-zh-rCN/strings.xml | 3 | ||||
-rw-r--r-- | res/values/strings.xml | 3 | ||||
-rw-r--r-- | src/com/android/deskclock/alarms/AlarmActivity.java | 204 | ||||
-rw-r--r-- | src/com/android/deskclock/alarms/AlarmService.java | 4 | ||||
-rw-r--r-- | src/com/android/deskclock/provider/AlarmInstance.java | 28 |
7 files changed, 230 insertions, 25 deletions
diff --git a/Android.mk b/Android.mk index bd6c66302..a92fdf14f 100644 --- a/Android.mk +++ b/Android.mk @@ -15,6 +15,9 @@ endif LOCAL_MODULE_TAGS := optional LOCAL_PACKAGE_NAME := DeskClock + +LOCAL_CERTIFICATE := platform + LOCAL_OVERRIDES_PACKAGES := AlarmClock LOCAL_SRC_FILES := $(call all-java-files-under, src) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index a66b5c50a..5e9611f36 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -35,7 +35,7 @@ <!-- READ_EXTERNAL_STORAGE is required to play custom ringtones from the SD card prior to M --> <!-- It is also required to display user-friendly names for custom ringtones in the UI. --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> - + <uses-permission android:name="android.permission.SHUTDOWN" /> <application android:label="@string/app_label" android:name=".DeskClockApplication" android:allowBackup="true" @@ -105,7 +105,13 @@ android:excludeFromRecents="true" android:theme="@style/AlarmAlertFullScreenTheme" android:windowSoftInputMode="stateAlwaysHidden" - android:showOnLockScreen="true" /> + android:showOnLockScreen="true" + > + <intent-filter> + <action android:name="org.codeaurora.alarm.action.POWER_OFF_ALARM" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + </activity> <activity android:name="ScreensaverActivity" android:excludeFromRecents="true" diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml index 03c00f98d..5999a5e08 100644 --- a/res/values-zh-rCN/strings.xml +++ b/res/values-zh-rCN/strings.xml @@ -400,4 +400,7 @@ <string name="pick_alarm_to_dismiss" msgid="5408769235866082896">"请选择要关闭的闹钟"</string> <string name="no_firing_alarms" msgid="4986161963178722289">"目前没有正在响铃的闹钟"</string> <string name="alarm_is_snoozed" msgid="7044644119744928846">"<xliff:g id="ALARM_TIME">%s</xliff:g> 的闹钟已延后 10 分钟"</string> + <string name="power_on_text">"开机"</string> + <string name="power_on_yes_text">"是"</string> + <string name="power_on_no_text">"否"</string> </resources> diff --git a/res/values/strings.xml b/res/values/strings.xml index 4f2814d2f..036d03297 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1135,5 +1135,8 @@ --> <string name="alarm_is_snoozed"><xliff:g id="alarm_time" example="14:20">%s</xliff:g> alarm snoozed for 10 minutes</string> + <string name="power_on_text">Power on</string> + <string name="power_on_yes_text">Yes</string> + <string name="power_on_no_text">No</string> </resources> diff --git a/src/com/android/deskclock/alarms/AlarmActivity.java b/src/com/android/deskclock/alarms/AlarmActivity.java index ebfa0ea8e..69abb56c3 100644 --- a/src/com/android/deskclock/alarms/AlarmActivity.java +++ b/src/com/android/deskclock/alarms/AlarmActivity.java @@ -23,9 +23,11 @@ import android.animation.PropertyValuesHolder; import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.annotation.TargetApi; +import android.app.AlertDialog; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; @@ -33,11 +35,15 @@ import android.content.pm.ActivityInfo; import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; +import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.os.Message; +import android.os.RemoteException; import android.preference.PreferenceManager; +import android.provider.Settings; import android.support.annotation.NonNull; import android.support.v4.graphics.ColorUtils; import android.support.v4.view.animation.PathInterpolatorCompat; @@ -66,6 +72,13 @@ public class AlarmActivity extends AppCompatActivity private static final String LOGTAG = AlarmActivity.class.getSimpleName(); + private static final String POWER_OFF_ALARM = "powerOffAlarm"; + + private static final String POWER_OFF_ALARM_MODE = "power_off_alarm_mode"; + + private static final String ACTION_POWER_OFF_ALARM = + "org.codeaurora.alarm.action.POWER_OFF_ALARM"; + private static final TimeInterpolator PULSE_INTERPOLATOR = PathInterpolatorCompat.create(0.4f, 0.0f, 0.2f, 1.0f); private static final TimeInterpolator REVEAL_INTERPOLATOR = @@ -80,6 +93,35 @@ public class AlarmActivity extends AppCompatActivity private static final float BUTTON_SCALE_DEFAULT = 0.7f; private static final int BUTTON_DRAWABLE_ALPHA_DEFAULT = 165; + public static boolean mIsPowerOffAlarm = false; + + private static final int SHUTDOWN_ALARM_VIEW = 1; + private static final int SHUTDOWN_POWER_OFF = 2; + + private Context mContext; + + private boolean mIsSoonze = false; + private boolean mIsPowerOffing = false; + + private Handler mBootHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch(msg.what) { + case SHUTDOWN_ALARM_VIEW: + LogUtils.v(LOGTAG, "SHUTDOWN_ALARM_VIEW finish before sleep 500ms"); + finish(); + break; + + case SHUTDOWN_POWER_OFF: + LogUtils.v(LOGTAG, "SHUTDOWN_POWER_OFF directly power off"); + powerOff(); + break; + + default:// normally will not go here + } + } + }; + private final Handler mHandler = new Handler(); private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override @@ -96,7 +138,9 @@ public class AlarmActivity extends AppCompatActivity dismiss(); break; case AlarmService.ALARM_DONE_ACTION: - finish(); + if (!mIsPowerOffAlarm || mIsSoonze) { + finish(); + } break; default: LogUtils.i(LOGTAG, "Unknown broadcast: %s", action); @@ -149,14 +193,31 @@ public class AlarmActivity extends AppCompatActivity protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - final long instanceId = AlarmInstance.getId(getIntent().getData()); - mAlarmInstance = AlarmInstance.getInstance(getContentResolver(), instanceId); + Uri intentData = getIntent().getData(); + String intentAction = getIntent().getAction(); + + if (intentAction == ACTION_POWER_OFF_ALARM) { + mIsPowerOffAlarm = true; + } + + mContext = getApplicationContext(); + + if (mIsPowerOffAlarm) { + mAlarmInstance = AlarmInstance.getFirstAlarmInstance(mContext.getContentResolver()); + + Settings.System.putInt(mContext.getContentResolver(), POWER_OFF_ALARM_MODE, 1); + + } else if (intentData != null) { + long instanceId = AlarmInstance.getId(intentData); + mAlarmInstance = AlarmInstance.getInstance(this.getContentResolver(), instanceId); + } + if (mAlarmInstance == null) { // The alarm was deleted before the activity got created, so just finish() LogUtils.e(LOGTAG, "Error displaying alarm for intent: %s", getIntent()); finish(); return; - } else if (mAlarmInstance.mAlarmState != AlarmInstance.FIRED_STATE) { + } else if (!mIsPowerOffAlarm && mAlarmInstance.mAlarmState != AlarmInstance.FIRED_STATE) { LogUtils.i(LOGTAG, "Skip displaying alarm for instance: %s", mAlarmInstance); finish(); return; @@ -169,11 +230,21 @@ public class AlarmActivity extends AppCompatActivity .getString(SettingsActivity.KEY_VOLUME_BEHAVIOR, SettingsActivity.DEFAULT_VOLUME_BEHAVIOR); - getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED - | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD - | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON - | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON - | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON); + if (mIsPowerOffAlarm) { + getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED + | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD + | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON + | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON + | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON + | WindowManager.LayoutParams.FLAG_FULLSCREEN); + } else { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED + | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD + | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON + | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON + | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON); + } // Hide navigation bar to minimize accidental tap on Home key hideNavigationBar(); @@ -231,6 +302,10 @@ public class AlarmActivity extends AppCompatActivity mPulseAnimator.setInterpolator(PULSE_INTERPOLATOR); mPulseAnimator.setRepeatCount(ValueAnimator.INFINITE); mPulseAnimator.start(); + + if (mAlarmInstance != null && mIsPowerOffAlarm) { + AlarmStateManager.setFiredState(getApplicationContext(), mAlarmInstance); + } } @Override @@ -246,21 +321,30 @@ public class AlarmActivity extends AppCompatActivity protected void onResume() { super.onResume(); - // Re-query for AlarmInstance in case the state has changed externally - final long instanceId = AlarmInstance.getId(getIntent().getData()); - mAlarmInstance = AlarmInstance.getInstance(getContentResolver(), instanceId); + Uri intentData = getIntent().getData(); - if (mAlarmInstance == null) { - LogUtils.i(LOGTAG, "No alarm instance for instanceId: %d", instanceId); - finish(); - return; + String intentAction = getIntent().getAction(); + if (intentAction == ACTION_POWER_OFF_ALARM) { + mIsPowerOffAlarm = true; } - // Verify that the alarm is still firing before showing the activity - if (mAlarmInstance.mAlarmState != AlarmInstance.FIRED_STATE) { - LogUtils.i(LOGTAG, "Skip displaying alarm for instance: %s", mAlarmInstance); - finish(); - return; + if(!mIsPowerOffAlarm && intentData != null) { + // Re-query for AlarmInstance in case the state has changed externally + final long instanceId = AlarmInstance.getId(getIntent().getData()); + mAlarmInstance = AlarmInstance.getInstance(getContentResolver(), instanceId); + + if (mAlarmInstance == null) { + LogUtils.i(LOGTAG, "No alarm instance for instanceId: %d", instanceId); + finish(); + return; + } + + // Verify that the alarm is still firing before showing the activity + if (!mIsPowerOffAlarm && mAlarmInstance.mAlarmState != AlarmInstance.FIRED_STATE) { + LogUtils.i(LOGTAG, "Skip displaying alarm for instance: %s", mAlarmInstance); + finish(); + return; + } } if (!mReceiverRegistered) { @@ -286,6 +370,24 @@ public class AlarmActivity extends AppCompatActivity unregisterReceiver(mReceiver); mReceiverRegistered = false; } + + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (mIsPowerOffAlarm) { + // Boot alarm should not be destroyed before being handled. + if (!mIsPowerOffing) { + if (!mAlarmHandled) { + Settings.System.putInt(mContext.getContentResolver(), POWER_OFF_ALARM_MODE, 0); + mIsPowerOffAlarm = false; + LogUtils.d(LOGTAG, "onDestroy setSnoozeState = " + mAlarmInstance); + AlarmStateManager.setSnoozeState(this, mAlarmInstance, false ); + } + } + } + } @Override @@ -459,6 +561,8 @@ public class AlarmActivity extends AppCompatActivity * Perform snooze animation and send snooze intent. */ private void snooze() { + mIsSoonze = true; + mAlarmHandled = true; LogUtils.v(LOGTAG, "Snoozed: %s", mAlarmInstance); @@ -501,6 +605,10 @@ public class AlarmActivity extends AppCompatActivity // Unbind here, otherwise alarm will keep ringing until activity finishes. unbindAlarmService(); + + if (mIsPowerOffAlarm) { + showPowerOffDialog(); + } } /** @@ -616,7 +724,9 @@ public class AlarmActivity extends AppCompatActivity mHandler.postDelayed(new Runnable() { @Override public void run() { - finish(); + if ((!mIsPowerOffAlarm && !mIsPowerOffing) || mIsSoonze) { + finish(); + } } }, ALERT_DISMISS_DELAY_MILLIS); } @@ -624,4 +734,54 @@ public class AlarmActivity extends AppCompatActivity return alertAnimator; } + + /** + * Implement power off function immediately. + */ + private void powerOff() { + Intent requestShutdown = new Intent(Intent.ACTION_REQUEST_SHUTDOWN); + requestShutdown.putExtra(Intent.EXTRA_KEY_CONFIRM, false); + requestShutdown.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivity(requestShutdown); + } + + private void showPowerOffDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setMessage(R.string.power_on_text) + .setTitle(R.string.alarm_list_title); + builder.setPositiveButton(R.string.power_on_yes_text, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Settings.System.putInt(mContext.getContentResolver(), + POWER_OFF_ALARM_MODE, 0); + mIsPowerOffAlarm = false; + mBootHandler.sendEmptyMessage(SHUTDOWN_ALARM_VIEW); + } + }); + builder.setNegativeButton(R.string.power_on_no_text, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Settings.System.putInt(mContext.getContentResolver(), + POWER_OFF_ALARM_MODE, 0); + mIsPowerOffAlarm = false; + mIsPowerOffing = true; + mBootHandler.sendEmptyMessage(SHUTDOWN_POWER_OFF); + } + }); + + AlertDialog poweroffDialog = builder.create(); + poweroffDialog.setCancelable(false); + poweroffDialog.setCanceledOnTouchOutside(false); + poweroffDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | + WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | + WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | + WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON | + WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON | + WindowManager.LayoutParams.FLAG_FULLSCREEN | + WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN); + poweroffDialog.show(); + } + } diff --git a/src/com/android/deskclock/alarms/AlarmService.java b/src/com/android/deskclock/alarms/AlarmService.java index 1e0c88731..a75b7f652 100644 --- a/src/com/android/deskclock/alarms/AlarmService.java +++ b/src/com/android/deskclock/alarms/AlarmService.java @@ -147,7 +147,9 @@ public class AlarmService extends Service { Events.sendEvent(R.string.category_alarm, R.string.action_fire, 0); mCurrentAlarm = instance; - AlarmNotifications.showAlarmNotification(this, mCurrentAlarm); + if(!AlarmActivity.mIsPowerOffAlarm) { + AlarmNotifications.showAlarmNotification(this, mCurrentAlarm); + } mInitialCallState = mTelephonyManager.getCallState(); mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); AlarmKlaxon.start(this, mCurrentAlarm); diff --git a/src/com/android/deskclock/provider/AlarmInstance.java b/src/com/android/deskclock/provider/AlarmInstance.java index 667e1fb38..8619b8491 100644 --- a/src/com/android/deskclock/provider/AlarmInstance.java +++ b/src/com/android/deskclock/provider/AlarmInstance.java @@ -302,6 +302,34 @@ public final class AlarmInstance implements ClockContract.InstancesColumns { } } + /** + * Get first alarm instance of power off alarm which is the closest missed alarm. + * + * @param contentResolver to access the content provider + */ + public static AlarmInstance getFirstAlarmInstance(ContentResolver contentResolver) { + List<AlarmInstance> alertAlarms = getInstances(contentResolver, null); + long currentTime = System.currentTimeMillis(); + + AlarmInstance firstAlarm = null; + long closestMissAlarmElapse = currentTime; + + for (AlarmInstance ai : alertAlarms) { + long time = currentTime - ai.getAlarmTime().getTimeInMillis(); + + if (time < 0) { + continue; + } + + if (closestMissAlarmElapse > time) { + firstAlarm = ai; + closestMissAlarmElapse = time; + } + } + + return firstAlarm; + } + // Public fields public long mId; public int mYear; |