diff options
Diffstat (limited to 'services/java/com/android')
8 files changed, 1053 insertions, 54 deletions
diff --git a/services/java/com/android/server/BatteryService.java b/services/java/com/android/server/BatteryService.java index dbffa97..537d69f 100644 --- a/services/java/com/android/server/BatteryService.java +++ b/services/java/com/android/server/BatteryService.java @@ -24,6 +24,9 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.graphics.Color; import android.os.BatteryManager; import android.os.Binder; import android.os.FileUtils; @@ -44,7 +47,7 @@ import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; - +import java.util.Calendar; /** * <p>BatteryService monitors the charging status, and charge level of the device @@ -140,10 +143,21 @@ public final class BatteryService extends Binder { private boolean mUpdatesStopped; private Led mLed; + private boolean mLightEnabled; + private boolean mLedPulseEnabled; + private int mBatteryLowARGB; + private int mBatteryMediumARGB; + private int mBatteryFullARGB; + private boolean mMultiColorLed; private boolean mSentLowBatteryBroadcast = false; private native void native_update(); + // Quiet hours support + private boolean mQuietHoursEnabled = false; + private int mQuietHoursStart = 0; + private int mQuietHoursEnd = 0; + private boolean mQuietHoursDim = true; public BatteryService(Context context, LightsService lights) { mContext = context; @@ -168,6 +182,9 @@ public final class BatteryService extends Binder { "DEVPATH=/devices/virtual/switch/invalid_charger"); } + SettingsObserver observer = new SettingsObserver(new Handler()); + observer.observe(); + // set initial status synchronized (mLock) { updateLocked(); @@ -672,6 +689,10 @@ public final class BatteryService extends Binder { } }; + private synchronized void updateLedPulse() { + mLed.updateLightsLocked(); + } + private final class Led { private final LightsService.Light mBatteryLight; @@ -684,12 +705,17 @@ public final class BatteryService extends Binder { public Led(Context context, LightsService lights) { mBatteryLight = lights.getLight(LightsService.LIGHT_ID_BATTERY); - mBatteryLowARGB = context.getResources().getInteger( + mBatteryLowARGB = mContext.getResources().getInteger( com.android.internal.R.integer.config_notificationsBatteryLowARGB); - mBatteryMediumARGB = context.getResources().getInteger( + mBatteryMediumARGB = mContext.getResources().getInteger( com.android.internal.R.integer.config_notificationsBatteryMediumARGB); - mBatteryFullARGB = context.getResources().getInteger( + mBatteryFullARGB = mContext.getResources().getInteger( com.android.internal.R.integer.config_notificationsBatteryFullARGB); + + // Does the Device support changing battery LED colors? + mMultiColorLed = context.getResources().getBoolean( + com.android.internal.R.bool.config_multiColorBatteryLed); + mBatteryLedOn = context.getResources().getInteger( com.android.internal.R.integer.config_notificationsBatteryLedOn); mBatteryLedOff = context.getResources().getInteger( @@ -702,28 +728,143 @@ public final class BatteryService extends Binder { public void updateLightsLocked() { final int level = mBatteryLevel; final int status = mBatteryStatus; - if (level < mLowBatteryWarningLevel) { + + if (!mLightEnabled) { + // No lights if explicitly disabled + mBatteryLight.turnOff(); + } else if (inQuietHours() && mQuietHoursDim) { + if (mLedPulseEnabled && level < mLowBatteryWarningLevel && + status != BatteryManager.BATTERY_STATUS_CHARGING) { + // The battery is low, the device is not charging and the low battery pulse + // is enabled - ignore Quiet Hours + mBatteryLight.setFlashing(mBatteryLowARGB, LightsService.LIGHT_FLASH_TIMED, + mBatteryLedOn, mBatteryLedOff); + } else { + // No lights if in Quiet Hours and battery not low + mBatteryLight.turnOff(); + } + } else if (level < mLowBatteryWarningLevel) { if (status == BatteryManager.BATTERY_STATUS_CHARGING) { - // Solid red when battery is charging + // Battery is charging and low mBatteryLight.setColor(mBatteryLowARGB); - } else { - // Flash red when battery is low and not charging + } else if (mLedPulseEnabled) { + // Battery is low and not charging mBatteryLight.setFlashing(mBatteryLowARGB, LightsService.LIGHT_FLASH_TIMED, mBatteryLedOn, mBatteryLedOff); + } else { + // "Pulse low battery light" is disabled, no lights. + mBatteryLight.turnOff(); } } else if (status == BatteryManager.BATTERY_STATUS_CHARGING - || status == BatteryManager.BATTERY_STATUS_FULL) { + || status == BatteryManager.BATTERY_STATUS_FULL) { if (status == BatteryManager.BATTERY_STATUS_FULL || level >= 90) { - // Solid green when full or charging and nearly full + // Battery is full or charging and nearly full mBatteryLight.setColor(mBatteryFullARGB); } else { - // Solid orange when charging and halfway full + // Battery is charging and halfway full mBatteryLight.setColor(mBatteryMediumARGB); } } else { - // No lights if not charging and not low + //No lights if not charging and not low mBatteryLight.turnOff(); } } } + + class SettingsObserver extends ContentObserver { + SettingsObserver(Handler handler) { + super(handler); + } + + void observe() { + ContentResolver resolver = mContext.getContentResolver(); + + // Battery light enabled + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.BATTERY_LIGHT_ENABLED), false, this); + + // Low battery pulse + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.BATTERY_LIGHT_PULSE), false, this); + + // Light colors + if (mMultiColorLed) { + // Register observer if we have a multi color led + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.BATTERY_LIGHT_LOW_COLOR), false, this); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.BATTERY_LIGHT_MEDIUM_COLOR), false, this); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.BATTERY_LIGHT_FULL_COLOR), false, this); + } + + // Quiet Hours + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.QUIET_HOURS_ENABLED), false, this); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.QUIET_HOURS_START), false, this); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.QUIET_HOURS_END), false, this); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.QUIET_HOURS_DIM), false, this); + + update(); + } + + @Override public void onChange(boolean selfChange) { + update(); + } + + public void update() { + ContentResolver resolver = mContext.getContentResolver(); + Resources res = mContext.getResources(); + + // Battery light enabled + mLightEnabled = Settings.System.getInt(resolver, + Settings.System.BATTERY_LIGHT_ENABLED, 1) != 0; + + // Low battery pulse + mLedPulseEnabled = Settings.System.getInt(resolver, + Settings.System.BATTERY_LIGHT_PULSE, 1) != 0; + + // Light colors + mBatteryLowARGB = Settings.System.getInt(resolver, + Settings.System.BATTERY_LIGHT_LOW_COLOR, + res.getInteger(com.android.internal.R.integer.config_notificationsBatteryLowARGB)); + mBatteryMediumARGB = Settings.System.getInt(resolver, + Settings.System.BATTERY_LIGHT_MEDIUM_COLOR, + res.getInteger(com.android.internal.R.integer.config_notificationsBatteryMediumARGB)); + mBatteryFullARGB = Settings.System.getInt(resolver, + Settings.System.BATTERY_LIGHT_FULL_COLOR, + res.getInteger(com.android.internal.R.integer.config_notificationsBatteryFullARGB)); + + // Quiet Hours + mQuietHoursEnabled = Settings.System.getInt(resolver, + Settings.System.QUIET_HOURS_ENABLED, 0) != 0; + mQuietHoursStart = Settings.System.getInt(resolver, + Settings.System.QUIET_HOURS_START, 0); + mQuietHoursEnd = Settings.System.getInt(resolver, + Settings.System.QUIET_HOURS_END, 0); + mQuietHoursDim = Settings.System.getInt(resolver, + Settings.System.QUIET_HOURS_DIM, 0) != 0; + + updateLedPulse(); + } + } + + private boolean inQuietHours() { + if (mQuietHoursEnabled && (mQuietHoursStart != mQuietHoursEnd)) { + // Get the date in "quiet hours" format. + Calendar calendar = Calendar.getInstance(); + int minutes = calendar.get(Calendar.HOUR_OF_DAY) * 60 + calendar.get(Calendar.MINUTE); + if (mQuietHoursEnd < mQuietHoursStart) { + // Starts at night, ends in the morning. + return (minutes > mQuietHoursStart) || (minutes < mQuietHoursEnd); + } else { + return (minutes > mQuietHoursStart) && (minutes < mQuietHoursEnd); + } + } + return false; + } + } diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index c9ff595..b11432d 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -397,6 +397,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub Settings.Secure.ENABLED_INPUT_METHODS), false, this); resolver.registerContentObserver(Settings.Secure.getUriFor( Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.STATUS_BAR_IME_SWITCHER), + false, new ContentObserver(mHandler) { + public void onChange(boolean selfChange) { + updateFromSettingsLocked(); + } + }); } @Override public void onChange(boolean selfChange) { @@ -844,8 +851,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mStatusBar = statusBar; statusBar.setIconVisibility("ime", false); updateImeWindowStatusLocked(); - mShowOngoingImeSwitcherForPhones = mRes.getBoolean( - com.android.internal.R.bool.show_ongoing_ime_switcher); if (mShowOngoingImeSwitcherForPhones) { mWindowManagerService.setOnHardKeyboardStatusChangeListener( mHardKeyboardListener); @@ -1597,6 +1602,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mCurMethodId = null; unbindCurrentMethodLocked(true, false); } + mShowOngoingImeSwitcherForPhones = Settings.System.getInt(mContext.getContentResolver(), + Settings.System.STATUS_BAR_IME_SWITCHER, 1) == 1; } /* package */ void setInputMethodLocked(String id, int subtypeId) { diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java index f3a38f0..af49135 100755 --- a/services/java/com/android/server/NotificationManagerService.java +++ b/services/java/com/android/server/NotificationManagerService.java @@ -27,7 +27,12 @@ import android.app.IActivityManager; import android.app.INotificationManager; import android.app.ITransientNotification; import android.app.Notification; +import android.app.NotificationGroup; +import android.app.NotificationManager; import android.app.PendingIntent; +import android.app.Profile; +import android.app.ProfileGroup; +import android.app.ProfileManager; import android.app.StatusBarManager; import android.content.BroadcastReceiver; import android.content.ContentResolver; @@ -44,6 +49,7 @@ import android.media.IAudioService; import android.media.IRingtonePlayer; import android.net.Uri; import android.os.Binder; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; @@ -81,7 +87,10 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.HashSet; +import java.util.Calendar; +import java.util.Map; import libcore.io.IoUtils; @@ -134,10 +143,13 @@ public class NotificationManagerService extends INotificationManager.Stub private IAudioService mAudioService; private Vibrator mVibrator; - // for enabling and disabling notification pulse behavior + // for enabling and disabling notification pulse behaviour private boolean mScreenOn = true; + private boolean mWasScreenOn = false; private boolean mInCall = false; private boolean mNotificationPulseEnabled; + private HashMap<String, NotificationLedValues> mNotificationPulseCustomLedValues; + private Map<String, String> mPackageNameMappings; private final ArrayList<NotificationRecord> mNotificationList = new ArrayList<NotificationRecord>(); @@ -147,6 +159,18 @@ public class NotificationManagerService extends INotificationManager.Stub private ArrayList<NotificationRecord> mLights = new ArrayList<NotificationRecord>(); private NotificationRecord mLedNotification; + private boolean mQuietHoursEnabled = false; + // Minutes from midnight when quiet hours begin. + private int mQuietHoursStart = 0; + // Minutes from midnight when quiet hours end. + private int mQuietHoursEnd = 0; + // Don't play sounds. + private boolean mQuietHoursMute = true; + // Don't vibrate. + private boolean mQuietHoursStill = true; + // Dim LED if hardware supports it. + private boolean mQuietHoursDim = true; + // Notification control database. For now just contains disabled packages. private AtomicFile mPolicyFile; private HashSet<String> mBlockedPackages = new HashSet<String>(); @@ -402,6 +426,12 @@ public class NotificationManagerService extends INotificationManager.Stub } } + class NotificationLedValues { + public int color; + public int onMS; + public int offMS; + } + private StatusBarManagerService.NotificationCallbacks mNotificationCallbacks = new StatusBarManagerService.NotificationCallbacks() { @@ -553,6 +583,8 @@ public class NotificationManagerService extends INotificationManager.Stub mScreenOn = true; } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { mScreenOn = false; + mWasScreenOn = true; + updateLightsLocked(); } else if (action.equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) { mInCall = (intent.getStringExtra(TelephonyManager.EXTRA_STATE).equals( TelephonyManager.EXTRA_STATE_OFFHOOK)); @@ -569,8 +601,8 @@ public class NotificationManagerService extends INotificationManager.Stub } }; - class SettingsObserver extends ContentObserver { - SettingsObserver(Handler handler) { + class LEDSettingsObserver extends ContentObserver { + LEDSettingsObserver(Handler handler) { super(handler); } @@ -578,24 +610,96 @@ public class NotificationManagerService extends INotificationManager.Stub ContentResolver resolver = mContext.getContentResolver(); resolver.registerContentObserver(Settings.System.getUriFor( Settings.System.NOTIFICATION_LIGHT_PULSE), false, this); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_COLOR), false, this); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_ON), false, this); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_OFF), false, this); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.NOTIFICATION_LIGHT_PULSE_CUSTOM_ENABLE), false, this); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.NOTIFICATION_LIGHT_PULSE_CUSTOM_VALUES), false, this); update(); } @Override public void onChange(boolean selfChange) { update(); + updateNotificationPulse(); } public void update() { ContentResolver resolver = mContext.getContentResolver(); - boolean pulseEnabled = Settings.System.getInt(resolver, - Settings.System.NOTIFICATION_LIGHT_PULSE, 0) != 0; - if (mNotificationPulseEnabled != pulseEnabled) { - mNotificationPulseEnabled = pulseEnabled; - updateNotificationPulse(); + // LED enabled + mNotificationPulseEnabled = Settings.System.getInt(resolver, + Settings.System.NOTIFICATION_LIGHT_PULSE, 0) != 0; + + // LED default color + mDefaultNotificationColor = Settings.System.getInt(resolver, + Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_COLOR, mDefaultNotificationColor); + + // LED default on MS + mDefaultNotificationLedOn = Settings.System.getInt(resolver, + Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_ON, mDefaultNotificationLedOn); + + // LED default off MS + mDefaultNotificationLedOff = Settings.System.getInt(resolver, + Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_OFF, mDefaultNotificationLedOff); + + // LED custom notification colors + mNotificationPulseCustomLedValues.clear(); + if (Settings.System.getInt(resolver, + Settings.System.NOTIFICATION_LIGHT_PULSE_CUSTOM_ENABLE, 0) != 0) { + parseNotificationPulseCustomValuesString(Settings.System.getString(resolver, + Settings.System.NOTIFICATION_LIGHT_PULSE_CUSTOM_VALUES)); } } } + class QuietHoursSettingsObserver extends ContentObserver { + QuietHoursSettingsObserver(Handler handler) { + super(handler); + } + + void observe() { + ContentResolver resolver = mContext.getContentResolver(); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.QUIET_HOURS_ENABLED), false, this); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.QUIET_HOURS_START), false, this); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.QUIET_HOURS_END), false, this); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.QUIET_HOURS_MUTE), false, this); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.QUIET_HOURS_STILL), false, this); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.QUIET_HOURS_DIM), false, this); + update(); + } + + @Override public void onChange(boolean selfChange) { + update(); + updateNotificationPulse(); + } + + public void update() { + ContentResolver resolver = mContext.getContentResolver(); + mQuietHoursEnabled = Settings.System.getInt(resolver, + Settings.System.QUIET_HOURS_ENABLED, 0) != 0; + mQuietHoursStart = Settings.System.getInt(resolver, + Settings.System.QUIET_HOURS_START, 0); + mQuietHoursEnd = Settings.System.getInt(resolver, + Settings.System.QUIET_HOURS_END, 0); + mQuietHoursMute = Settings.System.getInt(resolver, + Settings.System.QUIET_HOURS_MUTE, 0) != 0; + mQuietHoursStill = Settings.System.getInt(resolver, + Settings.System.QUIET_HOURS_STILL, 0) != 0; + mQuietHoursDim = Settings.System.getInt(resolver, + Settings.System.QUIET_HOURS_DIM, 0) != 0; + } + } + NotificationManagerService(Context context, StatusBarManagerService statusBar, LightsService lights) { @@ -622,6 +726,15 @@ public class NotificationManagerService extends INotificationManager.Stub mDefaultNotificationLedOff = resources.getInteger( com.android.internal.R.integer.config_defaultNotificationLedOff); + mNotificationPulseCustomLedValues = new HashMap<String, NotificationLedValues>(); + + mPackageNameMappings = new HashMap<String, String>(); + for(String mapping : resources.getStringArray( + com.android.internal.R.array.notification_light_package_mapping)) { + String[] map = mapping.split("\\|"); + mPackageNameMappings.put(map[0], map[1]); + } + // Don't start allowing notifications until the setup wizard has run once. // After that, including subsequent boots, init with notifications turned on. // This works on the first boot because the setup wizard will toggle this @@ -649,8 +762,10 @@ public class NotificationManagerService extends INotificationManager.Stub IntentFilter sdFilter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); mContext.registerReceiver(mIntentReceiver, sdFilter); - SettingsObserver observer = new SettingsObserver(mHandler); - observer.observe(); + LEDSettingsObserver ledObserver = new LEDSettingsObserver(mHandler); + ledObserver.observe(); + QuietHoursSettingsObserver qhObserver = new QuietHoursSettingsObserver(mHandler); + qhObserver.observe(); } void systemReady() { @@ -965,6 +1080,8 @@ public class NotificationManagerService extends INotificationManager.Stub } synchronized (mNotificationList) { + final boolean inQuietHours = inQuietHours(); + NotificationRecord r = new NotificationRecord(pkg, tag, id, callingUid, callingPid, userId, score, @@ -1040,6 +1157,16 @@ public class NotificationManagerService extends INotificationManager.Stub } } + try { + final ProfileManager profileManager = + (ProfileManager) mContext.getSystemService(Context.PROFILE_SERVICE); + + ProfileGroup group = profileManager.getActiveProfileGroup(pkg); + notification = group.processNotification(notification); + } catch(Throwable th) { + Log.e(TAG, "An error occurred profiling the notification.", th); + } + // If we're not supposed to beep, vibrate, etc. then don't. if (((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) == 0) && (!(old != null @@ -1053,7 +1180,8 @@ public class NotificationManagerService extends INotificationManager.Stub // sound final boolean useDefaultSound = (notification.defaults & Notification.DEFAULT_SOUND) != 0; - if (useDefaultSound || notification.sound != null) { + if (!(inQuietHours && mQuietHoursMute) + && (useDefaultSound || notification.sound != null)) { Uri uri; if (useDefaultSound) { uri = Settings.System.DEFAULT_NOTIFICATION_URI; @@ -1096,8 +1224,8 @@ public class NotificationManagerService extends INotificationManager.Stub final boolean useDefaultVibrate = (notification.defaults & Notification.DEFAULT_VIBRATE) != 0 || convertSoundToVibration; - - if ((useDefaultVibrate || notification.vibrate != null) + if (!(inQuietHours && mQuietHoursStill) + && (useDefaultVibrate || notification.vibrate != null) && !(audioManager.getRingerMode() == AudioManager.RINGER_MODE_SILENT)) { mVibrateNotification = r; @@ -1131,6 +1259,21 @@ public class NotificationManagerService extends INotificationManager.Stub idOut[0] = id; } + private boolean inQuietHours() { + if (mQuietHoursEnabled && (mQuietHoursStart != mQuietHoursEnd)) { + // Get the date in "quiet hours" format. + Calendar calendar = Calendar.getInstance(); + int minutes = calendar.get(Calendar.HOUR_OF_DAY) * 60 + calendar.get(Calendar.MINUTE); + if (mQuietHoursEnd < mQuietHoursStart) { + // Starts at night, ends in the morning. + return (minutes > mQuietHoursStart) || (minutes < mQuietHoursEnd); + } else { + return (minutes > mQuietHoursStart) && (minutes < mQuietHoursEnd); + } + } + return false; + } + private void sendAccessibilityEvent(Notification notification, CharSequence packageName) { AccessibilityManager manager = AccessibilityManager.getInstance(mContext); if (!manager.isEnabled()) { @@ -1376,17 +1519,45 @@ public class NotificationManagerService extends INotificationManager.Stub } } - // Don't flash while we are in a call or screen is on - if (mLedNotification == null || mInCall || mScreenOn) { + boolean wasScreenOn = mWasScreenOn; + mWasScreenOn = false; + + if (mLedNotification == null) { + mNotificationLight.turnOff(); + return; + } + + // We can assume that if the user turned the screen off while there was + // still an active notification then they wanted to keep the notification + // for later. In this case we shouldn't flash the notification light. + // For special notifications that automatically turn the screen on (such + // as missed calls), we use this flag to force the notification light + // even if the screen was turned off. + boolean forceWithScreenOff = (mLedNotification.notification.flags & + Notification.FLAG_FORCE_LED_SCREEN_OFF) != 0; + + // Don't flash while we are in a call, screen is on or we are in quiet hours with light dimmed + if (mInCall || mScreenOn || (inQuietHours() && mQuietHoursDim) || (wasScreenOn && !forceWithScreenOff)) { mNotificationLight.turnOff(); } else { - int ledARGB = mLedNotification.notification.ledARGB; - int ledOnMS = mLedNotification.notification.ledOnMS; - int ledOffMS = mLedNotification.notification.ledOffMS; - if ((mLedNotification.notification.defaults & Notification.DEFAULT_LIGHTS) != 0) { - ledARGB = mDefaultNotificationColor; - ledOnMS = mDefaultNotificationLedOn; - ledOffMS = mDefaultNotificationLedOff; + int ledARGB; + int ledOnMS; + int ledOffMS; + NotificationLedValues ledValues = getLedValuesForNotification(mLedNotification); + if (ledValues != null) { + ledARGB = ledValues.color != 0 ? ledValues.color : mDefaultNotificationColor; + ledOnMS = ledValues.onMS >= 0 ? ledValues.onMS : mDefaultNotificationLedOn; + ledOffMS = ledValues.offMS >= 0 ? ledValues.offMS : mDefaultNotificationLedOn; + } else { + if ((mLedNotification.notification.defaults & Notification.DEFAULT_LIGHTS) != 0) { + ledARGB = mDefaultNotificationColor; + ledOnMS = mDefaultNotificationLedOn; + ledOffMS = mDefaultNotificationLedOff; + } else { + ledARGB = mLedNotification.notification.ledARGB; + ledOnMS = mLedNotification.notification.ledOnMS; + ledOffMS = mLedNotification.notification.ledOffMS; + } } if (mNotificationPulseEnabled) { // pulse repeatedly @@ -1396,6 +1567,47 @@ public class NotificationManagerService extends INotificationManager.Stub } } + private void parseNotificationPulseCustomValuesString(String customLedValuesString) { + if (TextUtils.isEmpty(customLedValuesString)) { + return; + } + + for (String packageValuesString : customLedValuesString.split("\\|")) { + String[] packageValues = packageValuesString.split("="); + if (packageValues.length != 2) { + Log.e(TAG, "Error parsing custom led values for unknown package"); + continue; + } + String packageName = packageValues[0]; + String[] values = packageValues[1].split(";"); + if (values.length != 3) { + Log.e(TAG, "Error parsing custom led values '" + packageValues[1] + "' for " + packageName); + continue; + } + NotificationLedValues ledValues = new NotificationLedValues(); + try { + ledValues.color = Integer.parseInt(values[0]); + ledValues.onMS = Integer.parseInt(values[1]); + ledValues.offMS = Integer.parseInt(values[2]); + } catch (Exception e) { + Log.e(TAG, "Error parsing custom led values '" + packageValues[1] + "' for " + packageName); + continue; + } + mNotificationPulseCustomLedValues.put(packageName, ledValues); + } + } + + private NotificationLedValues getLedValuesForNotification(NotificationRecord ledNotification) { + return mNotificationPulseCustomLedValues.get(mapPackage(ledNotification.pkg)); + } + + private String mapPackage(String pkg) { + if(!mPackageNameMappings.containsKey(pkg)) { + return pkg; + } + return mPackageNameMappings.get(pkg); + } + // lock on mNotificationList private int indexOfNotificationLocked(String pkg, String tag, int id, int userId) { diff --git a/services/java/com/android/server/ProfileManagerService.java b/services/java/com/android/server/ProfileManagerService.java new file mode 100644 index 0000000..aa769a5 --- /dev/null +++ b/services/java/com/android/server/ProfileManagerService.java @@ -0,0 +1,550 @@ +/* + * Copyright (c) 2011, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; + +import android.app.IProfileManager; +import android.app.NotificationGroup; +import android.app.Profile; +import android.app.ProfileGroup; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.XmlResourceParser; +import android.os.RemoteException; +import android.text.TextUtils; +import android.util.Log; +import android.os.ParcelUuid; + +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** {@hide} */ +public class ProfileManagerService extends IProfileManager.Stub { + // Enable the below for detailed logging of this class + private static final boolean LOCAL_LOGV = false; + /** + * <p>Broadcast Action: A new profile has been selected. This can be triggered by the user + * or by calls to the ProfileManagerService / Profile.</p> + * @hide + */ + public static final String INTENT_ACTION_PROFILE_SELECTED = "android.intent.action.PROFILE_SELECTED"; + + public static final String PERMISSION_CHANGE_SETTINGS = "android.permission.WRITE_SETTINGS"; + + private static final String PROFILE_FILENAME = "/data/system/profiles.xml"; + + private static final String TAG = "ProfileService"; + + private Map<UUID, Profile> mProfiles; + + // Match UUIDs and names, used for reverse compatibility + private Map<String, UUID> mProfileNames; + + private Map<UUID, NotificationGroup> mGroups; + + private Profile mActiveProfile; + + // Well-known UUID of the wildcard group + private static final UUID mWildcardUUID = UUID.fromString("a126d48a-aaef-47c4-baed-7f0e44aeffe5"); + private NotificationGroup mWildcardGroup; + + private Context mContext; + private boolean mDirty; + + private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + + if (action.equals(Intent.ACTION_LOCALE_CHANGED)) { + persistIfDirty(); + initialize(); + } else if (action.equals(Intent.ACTION_SHUTDOWN)) { + persistIfDirty(); + } + } + }; + + public ProfileManagerService(Context context) { + mContext = context; + + mWildcardGroup = new NotificationGroup( + context.getString(com.android.internal.R.string.wildcardProfile), + com.android.internal.R.string.wildcardProfile, + mWildcardUUID); + + initialize(); + + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_LOCALE_CHANGED); + filter.addAction(Intent.ACTION_SHUTDOWN); + mContext.registerReceiver(mIntentReceiver, filter); + } + + private void initialize() { + initialize(false); + } + + private void initialize(boolean skipFile) { + mProfiles = new HashMap<UUID, Profile>(); + mProfileNames = new HashMap<String, UUID>(); + mGroups = new HashMap<UUID, NotificationGroup>(); + mDirty = false; + + boolean init = skipFile; + + if (! skipFile) { + try { + loadFromFile(); + } catch (RemoteException e) { + e.printStackTrace(); + } catch (XmlPullParserException e) { + init = true; + } catch (IOException e) { + init = true; + } + } + + if (init) { + try { + initialiseStructure(); + } catch (Throwable ex) { + Log.e(TAG, "Error loading xml from resource: ", ex); + } + } + } + + @Override + public void resetAll() { + enforceChangePermissions(); + initialize(true); + } + + @Override + @Deprecated + public boolean setActiveProfileByName(String profileName) throws RemoteException, SecurityException { + if (mProfileNames.containsKey(profileName)) { + if (LOCAL_LOGV) Log.v(TAG, "setActiveProfile(String) found profile name in mProfileNames."); + return setActiveProfile(mProfiles.get(mProfileNames.get(profileName)), true); + } else { + // Since profileName could not be casted into a UUID, we can call it a string. + Log.w(TAG, "Unable to find profile to set active, based on string: " + profileName); + return false; + } + } + + @Override + public boolean setActiveProfile(ParcelUuid profileParcelUuid) throws RemoteException, SecurityException { + UUID profileUuid = profileParcelUuid.getUuid(); + if(mProfiles.containsKey(profileUuid)){ + if (LOCAL_LOGV) Log.v(TAG, "setActiveProfileByUuid(ParcelUuid) found profile UUID in mProfileNames."); + return setActiveProfile(mProfiles.get(profileUuid), true); + } else { + Log.e(TAG, "Cannot set active profile to: " + profileUuid.toString() + " - does not exist."); + return false; + } + } + + private boolean setActiveProfile(UUID profileUuid, boolean doinit) throws RemoteException { + if(mProfiles.containsKey(profileUuid)){ + if (LOCAL_LOGV) Log.v(TAG, "setActiveProfile(UUID, boolean) found profile UUID in mProfiles."); + return setActiveProfile(mProfiles.get(profileUuid), doinit); + } else { + Log.e(TAG, "Cannot set active profile to: " + profileUuid.toString() + " - does not exist."); + return false; + } + } + + private boolean setActiveProfile(Profile newActiveProfile, boolean doinit) throws RemoteException { + /* + * NOTE: Since this is not a public function, and all public functions + * take either a string or a UUID, the active profile should always be + * in the collection. If writing another setActiveProfile which receives + * a Profile object, run enforceChangePermissions, add the profile to the + * list, and THEN add it. + */ + + try { + enforceChangePermissions(); + Log.d(TAG, "Set active profile to: " + newActiveProfile.getUuid().toString() + " - " + newActiveProfile.getName()); + Profile lastProfile = mActiveProfile; + mActiveProfile = newActiveProfile; + mDirty = true; + if (doinit) { + if (LOCAL_LOGV) Log.v(TAG, "setActiveProfile(Profile, boolean) - Running init"); + + /* + * We need to clear the caller's identity in order to + * - allow the profile switch to execute actions not included in the caller's permissions + * - broadcast INTENT_ACTION_PROFILE_SELECTED + */ + long token = clearCallingIdentity(); + + // Call profile's "doSelect" + mActiveProfile.doSelect(mContext); + + // Notify other applications of newly selected profile. + Intent broadcast = new Intent(INTENT_ACTION_PROFILE_SELECTED); + broadcast.putExtra("name", mActiveProfile.getName()); + broadcast.putExtra("uuid", mActiveProfile.getUuid().toString()); + broadcast.putExtra("lastName", lastProfile.getName()); + broadcast.putExtra("lastUuid", lastProfile.getUuid().toString()); + mContext.sendBroadcast(broadcast); + + restoreCallingIdentity(token); + persistIfDirty(); + } + return true; + } catch (Exception ex) { + ex.printStackTrace(); + return false; + } + } + + @Override + public boolean addProfile(Profile profile) throws RemoteException, SecurityException { + enforceChangePermissions(); + addProfileInternal(profile); + persistIfDirty(); + return true; + } + + private void addProfileInternal(Profile profile) { + // Make sure this profile has all of the correct groups. + for (NotificationGroup group : mGroups.values()) { + ensureGroupInProfile(profile, group, false); + } + ensureGroupInProfile(profile, mWildcardGroup, true); + mProfiles.put(profile.getUuid(), profile); + mProfileNames.put(profile.getName(), profile.getUuid()); + mDirty = true; + } + + private void ensureGroupInProfile(Profile profile, NotificationGroup group, boolean defaultGroup) { + if (profile.getProfileGroup(group.getUuid()) != null) { + return; + } + + /* enforce a matchup between profile and notification group, which not only + * works by UUID, but also by name for backwards compatibility */ + for (ProfileGroup pg : profile.getProfileGroups()) { + if (pg.matches(group, defaultGroup)) { + return; + } + } + + /* didn't find any, create new group */ + profile.addProfileGroup(new ProfileGroup(group.getUuid(), defaultGroup)); + } + + @Override + @Deprecated + public Profile getProfileByName(String profileName) throws RemoteException { + if (mProfileNames.containsKey(profileName)) { + return mProfiles.get(mProfileNames.get(profileName)); + } else if (mProfiles.containsKey(UUID.fromString((profileName)))) { + return mProfiles.get(UUID.fromString(profileName)); + } else { + return null; + } + } + + @Override + public Profile getProfile(ParcelUuid profileParcelUuid) { + UUID profileUuid = profileParcelUuid.getUuid(); + return getProfile(profileUuid); + } + + public Profile getProfile(UUID profileUuid) { + // use primary UUID first + if (mProfiles.containsKey(profileUuid)) { + return mProfiles.get(profileUuid); + } + // if no match was found: try secondary UUID + for (Profile p : mProfiles.values()) { + for (UUID uuid : p.getSecondaryUuids()) { + if (profileUuid.equals(uuid)) { + return p; + } + } + } + // nothing found + return null; + } + + @Override + public Profile[] getProfiles() throws RemoteException { + Profile[] tmpArr = mProfiles.values().toArray(new Profile[mProfiles.size()]); + Arrays.sort(tmpArr); + return tmpArr; + } + + @Override + public Profile getActiveProfile() throws RemoteException { + return mActiveProfile; + } + + @Override + public boolean removeProfile(Profile profile) throws RemoteException, SecurityException { + enforceChangePermissions(); + if (mProfileNames.remove(profile.getName()) != null && mProfiles.remove(profile.getUuid()) != null) { + mDirty = true; + persistIfDirty(); + return true; + } else{ + return false; + } + } + + @Override + public void updateProfile(Profile profile) throws RemoteException, SecurityException { + enforceChangePermissions(); + Profile old = mProfiles.get(profile.getUuid()); + if (old != null) { + mProfileNames.remove(old.getName()); + mProfileNames.put(profile.getName(), profile.getUuid()); + mProfiles.put(profile.getUuid(), profile); + /* no need to set mDirty, if the profile was actually changed, + * it's marked as dirty by itself */ + persistIfDirty(); + + // Also update we changed the active profile + if (mActiveProfile != null && mActiveProfile.getUuid().equals(profile.getUuid())) { + setActiveProfile(profile, true); + } + } + } + + @Override + public boolean profileExists(ParcelUuid profileUuid) throws RemoteException { + return mProfiles.containsKey(profileUuid.getUuid()); + } + + @Override + public boolean profileExistsByName(String profileName) throws RemoteException { + for (Map.Entry<String, UUID> entry : mProfileNames.entrySet()) { + if (entry.getKey().equalsIgnoreCase(profileName)) { + return true; + } + } + return false; + } + + @Override + public boolean notificationGroupExistsByName(String notificationGroupName) throws RemoteException { + for (NotificationGroup group : mGroups.values()) { + if (group.getName().equalsIgnoreCase(notificationGroupName)) { + return true; + } + } + return false; + } + + @Override + public NotificationGroup[] getNotificationGroups() throws RemoteException { + return mGroups.values().toArray(new NotificationGroup[mGroups.size()]); + } + + @Override + public void addNotificationGroup(NotificationGroup group) throws RemoteException, SecurityException { + enforceChangePermissions(); + addNotificationGroupInternal(group); + persistIfDirty(); + } + + private void addNotificationGroupInternal(NotificationGroup group) { + if (mGroups.put(group.getUuid(), group) == null) { + // If the above is true, then the ProfileGroup shouldn't exist in + // the profile. Ensure it is added. + for (Profile profile : mProfiles.values()) { + ensureGroupInProfile(profile, group, false); + } + } + mDirty = true; + } + + @Override + public void removeNotificationGroup(NotificationGroup group) throws RemoteException, SecurityException { + enforceChangePermissions(); + mDirty |= (mGroups.remove(group.getUuid()) != null); + // Remove the corresponding ProfileGroup from all the profiles too if + // they use it. + for (Profile profile : mProfiles.values()) { + profile.removeProfileGroup(group.getUuid()); + } + persistIfDirty(); + } + + @Override + public void updateNotificationGroup(NotificationGroup group) throws RemoteException, SecurityException { + enforceChangePermissions(); + NotificationGroup old = mGroups.get(group.getUuid()); + if (old != null) { + mGroups.put(group.getUuid(), group); + /* no need to set mDirty, if the group was actually changed, + * it's marked as dirty by itself */ + persistIfDirty(); + } + } + + @Override + public NotificationGroup getNotificationGroupForPackage(String pkg) throws RemoteException { + for (NotificationGroup group : mGroups.values()) { + if (group.hasPackage(pkg)) { + return group; + } + } + return null; + } + + private void loadFromFile() throws RemoteException, XmlPullParserException, IOException { + XmlPullParserFactory xppf = XmlPullParserFactory.newInstance(); + XmlPullParser xpp = xppf.newPullParser(); + FileReader fr = new FileReader(PROFILE_FILENAME); + xpp.setInput(fr); + loadXml(xpp, mContext); + fr.close(); + persistIfDirty(); + } + + private void loadXml(XmlPullParser xpp, Context context) throws + XmlPullParserException, IOException, RemoteException { + int event = xpp.next(); + String active = null; + while (event != XmlPullParser.END_TAG || !"profiles".equals(xpp.getName())) { + if (event == XmlPullParser.START_TAG) { + String name = xpp.getName(); + if (name.equals("active")) { + active = xpp.nextText(); + Log.d(TAG, "Found active: " + active); + } else if (name.equals("profile")) { + Profile prof = Profile.fromXml(xpp, context); + addProfileInternal(prof); + // Failsafe if no active found + if (active == null) { + active = prof.getUuid().toString(); + } + } else if (name.equals("notificationGroup")) { + NotificationGroup ng = NotificationGroup.fromXml(xpp, context); + addNotificationGroupInternal(ng); + } + } else if (event == XmlPullParser.END_DOCUMENT) { + throw new IOException("Premature end of file while reading " + PROFILE_FILENAME); + } + event = xpp.next(); + } + // Don't do initialisation on startup. The AudioManager doesn't exist yet + // and besides, the volume settings will have survived the reboot. + try { + // Try / catch block to detect if XML file needs to be upgraded. + setActiveProfile(UUID.fromString(active), false); + } catch (IllegalArgumentException e) { + if (mProfileNames.containsKey(active)) { + setActiveProfile(mProfileNames.get(active), false); + } else { + // Final fail-safe: We must have SOME profile active. + // If we couldn't select one by now, we'll pick the first in the set. + setActiveProfile(mProfiles.values().iterator().next(), false); + } + // This is a hint that we probably just upgraded the XML file. Save changes. + mDirty = true; + } + } + + private void initialiseStructure() throws RemoteException, XmlPullParserException, IOException { + XmlResourceParser xml = mContext.getResources().getXml( + com.android.internal.R.xml.profile_default); + try { + loadXml(xml, mContext); + mDirty = true; + persistIfDirty(); + } finally { + xml.close(); + } + } + + private String getXmlString() throws RemoteException { + StringBuilder builder = new StringBuilder(); + builder.append("<profiles>\n<active>"); + builder.append(TextUtils.htmlEncode(getActiveProfile().getUuid().toString())); + builder.append("</active>\n"); + + for (Profile p : mProfiles.values()) { + p.getXmlString(builder, mContext); + } + for (NotificationGroup g : mGroups.values()) { + g.getXmlString(builder, mContext); + } + builder.append("</profiles>\n"); + return builder.toString(); + } + + @Override + public NotificationGroup getNotificationGroup(ParcelUuid uuid) throws RemoteException { + if (uuid.getUuid().equals(mWildcardGroup.getUuid())) { + return mWildcardGroup; + } + return mGroups.get(uuid.getUuid()); + } + + private synchronized void persistIfDirty() { + boolean dirty = mDirty; + if (!dirty) { + for (Profile profile : mProfiles.values()) { + if (profile.isDirty()) { + dirty = true; + break; + } + } + } + if (!dirty) { + for (NotificationGroup group : mGroups.values()) { + if (group.isDirty()) { + dirty = true; + break; + } + } + } + if (dirty) { + try { + Log.d(TAG, "Saving profile data..."); + FileWriter fw = new FileWriter(PROFILE_FILENAME); + fw.write(getXmlString()); + fw.close(); + Log.d(TAG, "Save completed."); + mDirty = false; + } catch (Throwable e) { + e.printStackTrace(); + } + } + } + + private void enforceChangePermissions() throws SecurityException { + mContext.enforceCallingOrSelfPermission(PERMISSION_CHANGE_SETTINGS, + "You do not have permissions to change the Profile Manager."); + } +} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 894c4d0..3dd22b0 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -346,6 +346,7 @@ class ServerThread extends Thread { StatusBarManagerService statusBar = null; InputMethodManagerService imm = null; AppWidgetService appWidget = null; + ProfileManagerService profile = null; NotificationManagerService notification = null; WallpaperManagerService wallpaper = null; LocationManagerService location = null; @@ -553,6 +554,14 @@ class ServerThread extends Thread { } try { + Slog.i(TAG, "Profile Manager"); + profile = new ProfileManagerService(context); + ServiceManager.addService(Context.PROFILE_SERVICE, profile); + } catch (Throwable e) { + Slog.e(TAG, "Failure starting Profile Manager", e); + } + + try { Slog.i(TAG, "Notification Manager"); notification = new NotificationManagerService(context, statusBar, lights); ServiceManager.addService(Context.NOTIFICATION_SERVICE, notification); diff --git a/services/java/com/android/server/VibratorService.java b/services/java/com/android/server/VibratorService.java index df91dec..64cae1d 100755 --- a/services/java/com/android/server/VibratorService.java +++ b/services/java/com/android/server/VibratorService.java @@ -39,6 +39,7 @@ import android.provider.Settings.SettingNotFoundException; import android.util.Slog; import android.view.InputDevice; +import java.util.Calendar; import java.util.ArrayList; import java.util.LinkedList; import java.util.ListIterator; @@ -164,6 +165,29 @@ public class VibratorService extends IVibratorService.Stub return doVibratorExists(); } + private boolean inQuietHours() { + boolean quietHoursEnabled = Settings.System.getInt(mContext.getContentResolver(), + Settings.System.QUIET_HOURS_ENABLED, 0) != 0; + int quietHoursStart = Settings.System.getInt(mContext.getContentResolver(), + Settings.System.QUIET_HOURS_START, 0); + int quietHoursEnd = Settings.System.getInt(mContext.getContentResolver(), + Settings.System.QUIET_HOURS_END, 0); + boolean quietHoursHaptic = Settings.System.getInt(mContext.getContentResolver(), + Settings.System.QUIET_HOURS_HAPTIC, 0) != 0; + if (quietHoursEnabled && quietHoursHaptic && (quietHoursStart != quietHoursEnd)) { + // Get the date in "quiet hours" format. + Calendar calendar = Calendar.getInstance(); + int minutes = calendar.get(Calendar.HOUR_OF_DAY) * 60 + calendar.get(Calendar.MINUTE); + if (quietHoursEnd < quietHoursStart) { + // Starts at night, ends in the morning. + return (minutes > quietHoursStart) || (minutes < quietHoursEnd); + } else { + return (minutes > quietHoursStart) && (minutes < quietHoursEnd); + } + } + return false; + } + public void vibrate(long milliseconds, IBinder token) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE) != PackageManager.PERMISSION_GRANTED) { @@ -174,7 +198,7 @@ public class VibratorService extends IVibratorService.Stub // timeout of 0 or negative. This will ensure that a vibration has // either a timeout of > 0 or a non-null pattern. if (milliseconds <= 0 || (mCurrentVibration != null - && mCurrentVibration.hasLongerTimeout(milliseconds))) { + && mCurrentVibration.hasLongerTimeout(milliseconds)) || inQuietHours()) { // Ignore this vibration since the current vibration will play for // longer than milliseconds. return; @@ -204,6 +228,9 @@ public class VibratorService extends IVibratorService.Stub != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Requires VIBRATE permission"); } + if (inQuietHours()) { + return; + } int uid = Binder.getCallingUid(); // so wakelock calls will succeed long identity = Binder.clearCallingIdentity(); diff --git a/services/java/com/android/server/power/ShutdownThread.java b/services/java/com/android/server/power/ShutdownThread.java index c7f7390..1aa1fb0 100644 --- a/services/java/com/android/server/power/ShutdownThread.java +++ b/services/java/com/android/server/power/ShutdownThread.java @@ -47,6 +47,7 @@ import com.android.internal.telephony.ITelephony; import android.util.Log; import android.view.WindowManager; +import android.view.KeyEvent; public final class ShutdownThread extends Thread { // constants @@ -129,22 +130,63 @@ public final class ShutdownThread extends Thread { if (sConfirmDialog != null) { sConfirmDialog.dismiss(); } - sConfirmDialog = new AlertDialog.Builder(context) - .setTitle(mRebootSafeMode - ? com.android.internal.R.string.reboot_safemode_title - : com.android.internal.R.string.power_off) - .setMessage(resourceId) - .setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - beginShutdownSequence(context); - } - }) - .setNegativeButton(com.android.internal.R.string.no, null) - .create(); + if (mReboot && !mRebootSafeMode){ + sConfirmDialog = new AlertDialog.Builder(context) + .setIcon(android.R.drawable.ic_dialog_alert) + .setTitle(com.android.internal.R.string.reboot_system) + .setSingleChoiceItems(com.android.internal.R.array.shutdown_reboot_options, 0, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + if (which < 0) + return; + + String actions[] = context.getResources().getStringArray(com.android.internal.R.array.shutdown_reboot_actions); + + if (actions != null && which < actions.length) + mRebootReason = actions[which]; + } + }) + .setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + mReboot = true; + beginShutdownSequence(context); + } + }) + .setNegativeButton(com.android.internal.R.string.no, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + mReboot = false; + dialog.cancel(); + } + }) + .create(); + sConfirmDialog.setOnKeyListener(new DialogInterface.OnKeyListener() { + public boolean onKey (DialogInterface dialog, int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + mReboot = false; + dialog.cancel(); + } + return true; + } + }); + } else { + sConfirmDialog = new AlertDialog.Builder(context) + .setTitle(mRebootSafeMode + ? com.android.internal.R.string.reboot_safemode_title + : com.android.internal.R.string.power_off) + .setMessage(resourceId) + .setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + beginShutdownSequence(context); + } + }) + .setNegativeButton(com.android.internal.R.string.no, null) + .create(); + } + closer.dialog = sConfirmDialog; sConfirmDialog.setOnDismissListener(closer); sConfirmDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); sConfirmDialog.show(); + } else { beginShutdownSequence(context); } @@ -213,8 +255,13 @@ public final class ShutdownThread extends Thread { // throw up an indeterminate system dialog to indicate radio is // shutting down. ProgressDialog pd = new ProgressDialog(context); - pd.setTitle(context.getText(com.android.internal.R.string.power_off)); - pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress)); + if (mReboot) { + pd.setTitle(context.getText(com.android.internal.R.string.reboot_system)); + pd.setMessage(context.getText(com.android.internal.R.string.reboot_progress)); + } else { + pd.setTitle(context.getText(com.android.internal.R.string.power_off)); + pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress)); + } pd.setIndeterminate(true); pd.setCancelable(false); pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); @@ -294,7 +341,7 @@ public final class ShutdownThread extends Thread { } Log.i(TAG, "Sending shutdown broadcast..."); - + // First send the high-level shut down broadcast. mActionDone = false; mContext.sendOrderedBroadcastAsUser(new Intent(Intent.ACTION_SHUTDOWN), @@ -314,9 +361,9 @@ public final class ShutdownThread extends Thread { } } } - + Log.i(TAG, "Shutting down activity manager..."); - + final IActivityManager am = ActivityManagerNative.asInterface(ServiceManager.checkService("activity")); if (am != null) { diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index 51edb44..38233d0 100755 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -5430,6 +5430,12 @@ public class WindowManagerService extends IWindowManager.Stub mInputManager.setInputFilter(filter); } + // Called by window manager policy. Not exposed externally. + @Override + public void reboot() { + ShutdownThread.reboot(mContext, null, true); + } + public void setCurrentUser(final int newUserId) { synchronized (mWindowMap) { mCurrentUserId = newUserId; |