From ef2ea1faf6449f97a2423ecbdecce664c58bcbe1 Mon Sep 17 00:00:00 2001 From: Adam Lesinski Date: Thu, 5 Dec 2013 16:48:06 -0800 Subject: Introduce a Lifecycle for system services Cherry-picked from klp-modular-dev Provide an abstract class for system services to extend from, similar to the android.app.Service. This will allow services to receive events in a uniform way, and will allow services to be created and started in the correct order regardless of whether or not a particular service exists. Similar to android.app.Service, services are meant to implement Binder interfaces as inner classes. This prevents services from having incestuous access to each other and makes them use the public API. Change-Id: Iaacfee8d5f080a28d7cc606761f4624673ed390f --- services/java/Android.mk | 14 +- .../com/android/server/AlarmManagerService.java | 575 ++--- .../java/com/android/server/BatteryService.java | 33 +- .../server/DeviceStorageMonitorService.java | 491 ---- .../android/server/InputMethodManagerService.java | 2 +- .../java/com/android/server/LightsService.java | 213 -- .../java/com/android/server/LocalServices.java | 58 + .../android/server/NotificationManagerService.java | 2368 ------------------- .../android/server/StatusBarManagerService.java | 661 ------ services/java/com/android/server/SystemServer.java | 96 +- .../java/com/android/server/SystemService.java | 151 ++ .../com/android/server/SystemServiceManager.java | 141 ++ .../java/com/android/server/TwilightService.java | 572 ----- .../com/android/server/UiModeManagerService.java | 286 +-- services/java/com/android/server/Watchdog.java | 10 +- .../java/com/android/server/am/ServiceRecord.java | 9 +- services/java/com/android/server/lights/Light.java | 41 + .../com/android/server/lights/LightsManager.java | 31 + .../com/android/server/lights/LightsService.java | 205 ++ .../server/notification/NotificationDelegate.java | 27 + .../notification/NotificationManagerInternal.java | 8 + .../notification/NotificationManagerService.java | 2427 ++++++++++++++++++++ .../android/server/pm/PackageManagerService.java | 55 +- .../server/power/DisplayPowerController.java | 18 +- .../android/server/power/DisplayPowerState.java | 6 +- .../android/server/power/PowerManagerService.java | 29 +- .../server/statusbar/StatusBarManagerInternal.java | 29 + .../server/statusbar/StatusBarManagerService.java | 701 ++++++ .../storage/DeviceStorageMonitorInternal.java | 24 + .../storage/DeviceStorageMonitorService.java | 505 ++++ .../android/server/twilight/TwilightListener.java | 21 + .../android/server/twilight/TwilightManager.java | 24 + .../android/server/twilight/TwilightService.java | 467 ++++ .../com/android/server/twilight/TwilightState.java | 112 + services/jni/Android.mk | 2 +- services/jni/com_android_server_LightsService.cpp | 141 -- .../com_android_server_lights_LightsService.cpp | 141 ++ 37 files changed, 5686 insertions(+), 5008 deletions(-) delete mode 100644 services/java/com/android/server/DeviceStorageMonitorService.java delete mode 100644 services/java/com/android/server/LightsService.java create mode 100644 services/java/com/android/server/LocalServices.java delete mode 100644 services/java/com/android/server/NotificationManagerService.java delete mode 100644 services/java/com/android/server/StatusBarManagerService.java create mode 100644 services/java/com/android/server/SystemService.java create mode 100644 services/java/com/android/server/SystemServiceManager.java delete mode 100644 services/java/com/android/server/TwilightService.java create mode 100644 services/java/com/android/server/lights/Light.java create mode 100644 services/java/com/android/server/lights/LightsManager.java create mode 100644 services/java/com/android/server/lights/LightsService.java create mode 100644 services/java/com/android/server/notification/NotificationDelegate.java create mode 100644 services/java/com/android/server/notification/NotificationManagerInternal.java create mode 100644 services/java/com/android/server/notification/NotificationManagerService.java create mode 100644 services/java/com/android/server/statusbar/StatusBarManagerInternal.java create mode 100644 services/java/com/android/server/statusbar/StatusBarManagerService.java create mode 100644 services/java/com/android/server/storage/DeviceStorageMonitorInternal.java create mode 100644 services/java/com/android/server/storage/DeviceStorageMonitorService.java create mode 100644 services/java/com/android/server/twilight/TwilightListener.java create mode 100644 services/java/com/android/server/twilight/TwilightManager.java create mode 100644 services/java/com/android/server/twilight/TwilightService.java create mode 100644 services/java/com/android/server/twilight/TwilightState.java delete mode 100644 services/jni/com_android_server_LightsService.cpp create mode 100644 services/jni/com_android_server_lights_LightsService.cpp diff --git a/services/java/Android.mk b/services/java/Android.mk index 8c3d0f09197c..9651239f6db2 100644 --- a/services/java/Android.mk +++ b/services/java/Android.mk @@ -1,18 +1,18 @@ LOCAL_PATH:= $(call my-dir) -# the library +# Build services.jar # ============================================================ include $(CLEAR_VARS) -LOCAL_SRC_FILES := \ - $(call all-subdir-java-files) \ - com/android/server/EventLogTags.logtags \ - com/android/server/am/EventLogTags.logtags - LOCAL_MODULE:= services +LOCAL_MODULE_CLASS := JAVA_LIBRARIES + +LOCAL_SRC_FILES := \ + $(call all-subdir-java-files) \ + com/android/server/EventLogTags.logtags \ + com/android/server/am/EventLogTags.logtags LOCAL_JAVA_LIBRARIES := android.policy conscrypt telephony-common include $(BUILD_JAVA_LIBRARY) - include $(BUILD_DROIDDOC) diff --git a/services/java/com/android/server/AlarmManagerService.java b/services/java/com/android/server/AlarmManagerService.java index 5ae9a6df01ad..1fb164d76b1e 100644 --- a/services/java/com/android/server/AlarmManagerService.java +++ b/services/java/com/android/server/AlarmManagerService.java @@ -31,6 +31,7 @@ import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Handler; +import android.os.IBinder; import android.os.Message; import android.os.PowerManager; import android.os.SystemClock; @@ -64,54 +65,51 @@ import static android.app.AlarmManager.ELAPSED_REALTIME; import com.android.internal.util.LocalLog; -class AlarmManagerService extends IAlarmManager.Stub { +class AlarmManagerService extends SystemService { // The threshold for how long an alarm can be late before we print a // warning message. The time duration is in milliseconds. private static final long LATE_ALARM_THRESHOLD = 10 * 1000; private static final int RTC_WAKEUP_MASK = 1 << RTC_WAKEUP; private static final int RTC_MASK = 1 << RTC; - private static final int ELAPSED_REALTIME_WAKEUP_MASK = 1 << ELAPSED_REALTIME_WAKEUP; + private static final int ELAPSED_REALTIME_WAKEUP_MASK = 1 << ELAPSED_REALTIME_WAKEUP; private static final int ELAPSED_REALTIME_MASK = 1 << ELAPSED_REALTIME; - private static final int TIME_CHANGED_MASK = 1 << 16; - private static final int IS_WAKEUP_MASK = RTC_WAKEUP_MASK|ELAPSED_REALTIME_WAKEUP_MASK; + static final int TIME_CHANGED_MASK = 1 << 16; + static final int IS_WAKEUP_MASK = RTC_WAKEUP_MASK|ELAPSED_REALTIME_WAKEUP_MASK; // Mask for testing whether a given alarm type is wakeup vs non-wakeup - private static final int TYPE_NONWAKEUP_MASK = 0x1; // low bit => non-wakeup - - private static final String TAG = "AlarmManager"; - private static final String ClockReceiver_TAG = "ClockReceiver"; - private static final boolean localLOGV = false; - private static final boolean DEBUG_BATCH = localLOGV || false; - private static final boolean DEBUG_VALIDATE = localLOGV || false; - private static final int ALARM_EVENT = 1; - private static final String TIMEZONE_PROPERTY = "persist.sys.timezone"; + static final int TYPE_NONWAKEUP_MASK = 0x1; // low bit => non-wakeup + + static final String TAG = "AlarmManager"; + static final String ClockReceiver_TAG = "ClockReceiver"; + static final boolean localLOGV = false; + static final boolean DEBUG_BATCH = localLOGV || false; + static final boolean DEBUG_VALIDATE = localLOGV || false; + static final int ALARM_EVENT = 1; + static final String TIMEZONE_PROPERTY = "persist.sys.timezone"; - private static final Intent mBackgroundIntent + static final Intent mBackgroundIntent = new Intent().addFlags(Intent.FLAG_FROM_BACKGROUND); - private static final IncreasingTimeOrder sIncreasingTimeOrder = new IncreasingTimeOrder(); + static final IncreasingTimeOrder sIncreasingTimeOrder = new IncreasingTimeOrder(); - private static final boolean WAKEUP_STATS = false; + static final boolean WAKEUP_STATS = false; - private final Context mContext; + final LocalLog mLog = new LocalLog(TAG); - private final LocalLog mLog = new LocalLog(TAG); + final Object mLock = new Object(); - private Object mLock = new Object(); - - private int mDescriptor; + int mDescriptor; private long mNextWakeup; private long mNextNonWakeup; - private int mBroadcastRefCount = 0; - private PowerManager.WakeLock mWakeLock; - private ArrayList mInFlight = new ArrayList(); - private final AlarmThread mWaitThread = new AlarmThread(); - private final AlarmHandler mHandler = new AlarmHandler(); - private ClockReceiver mClockReceiver; + int mBroadcastRefCount = 0; + PowerManager.WakeLock mWakeLock; + ArrayList mInFlight = new ArrayList(); + final AlarmHandler mHandler = new AlarmHandler(); + ClockReceiver mClockReceiver; private UninstallReceiver mUninstallReceiver; - private final ResultReceiver mResultReceiver = new ResultReceiver(); - private final PendingIntent mTimeTickSender; - private final PendingIntent mDateChangeSender; + final ResultReceiver mResultReceiver = new ResultReceiver(); + PendingIntent mTimeTickSender; + PendingIntent mDateChangeSender; class WakeupEvent { public long when; @@ -125,8 +123,8 @@ class AlarmManagerService extends IAlarmManager.Stub { } } - private final LinkedList mRecentWakeups = new LinkedList(); - private final long RECENT_WAKEUP_PERIOD = 1000L * 60 * 60 * 24; // one day + final LinkedList mRecentWakeups = new LinkedList(); + final long RECENT_WAKEUP_PERIOD = 1000L * 60 * 60 * 24; // one day static final class Batch { long start; // These endpoints are always in ELAPSED @@ -317,9 +315,9 @@ class AlarmManagerService extends IAlarmManager.Stub { } // minimum recurrence period or alarm futurity for us to be able to fuzz it - private static final long MIN_FUZZABLE_INTERVAL = 10000; - private static final BatchTimeOrder sBatchOrder = new BatchTimeOrder(); - private final ArrayList mAlarmBatches = new ArrayList(); + static final long MIN_FUZZABLE_INTERVAL = 10000; + static final BatchTimeOrder sBatchOrder = new BatchTimeOrder(); + final ArrayList mAlarmBatches = new ArrayList(); static long convertToElapsed(long when, int type) { final boolean isRtc = (type == RTC || type == RTC_WAKEUP); @@ -403,7 +401,7 @@ class AlarmManagerService extends IAlarmManager.Stub { } } - private static final class InFlight extends Intent { + static final class InFlight extends Intent { final PendingIntent mPendingIntent; final WorkSource mWorkSource; final Pair mTarget; @@ -427,7 +425,7 @@ class AlarmManagerService extends IAlarmManager.Stub { } } - private static final class FilterStats { + static final class FilterStats { final BroadcastStats mBroadcastStats; final Pair mTarget; @@ -443,7 +441,7 @@ class AlarmManagerService extends IAlarmManager.Stub { } } - private static final class BroadcastStats { + static final class BroadcastStats { final String mPackageName; long aggregateTime; @@ -459,47 +457,48 @@ class AlarmManagerService extends IAlarmManager.Stub { } } - private final HashMap mBroadcastStats + final HashMap mBroadcastStats = new HashMap(); - public AlarmManagerService(Context context) { - mContext = context; + @Override + public void onStart() { mDescriptor = init(); mNextWakeup = mNextNonWakeup = 0; // We have to set current TimeZone info to kernel // because kernel doesn't keep this after reboot - String tz = SystemProperties.get(TIMEZONE_PROPERTY); - if (tz != null) { - setTimeZone(tz); - } + setTimeZoneImpl(SystemProperties.get(TIMEZONE_PROPERTY)); - PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); + PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); - mTimeTickSender = PendingIntent.getBroadcastAsUser(context, 0, + mTimeTickSender = PendingIntent.getBroadcastAsUser(getContext(), 0, new Intent(Intent.ACTION_TIME_TICK).addFlags( Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND), 0, UserHandle.ALL); Intent intent = new Intent(Intent.ACTION_DATE_CHANGED); intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); - mDateChangeSender = PendingIntent.getBroadcastAsUser(context, 0, intent, + mDateChangeSender = PendingIntent.getBroadcastAsUser(getContext(), 0, intent, Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT, UserHandle.ALL); // now that we have initied the driver schedule the alarm - mClockReceiver= new ClockReceiver(); + mClockReceiver = new ClockReceiver(); mClockReceiver.scheduleTimeTickEvent(); mClockReceiver.scheduleDateChangedEvent(); mUninstallReceiver = new UninstallReceiver(); if (mDescriptor != -1) { - mWaitThread.start(); + AlarmThread waitThread = new AlarmThread(); + waitThread.start(); } else { Slog.w(TAG, "Failed to open alarm driver. Falling back to a handler."); } + + publishBinderService(Context.ALARM_SERVICE, mService); } - + + @Override protected void finalize() throws Throwable { try { close(mDescriptor); @@ -508,19 +507,51 @@ class AlarmManagerService extends IAlarmManager.Stub { } } - @Override - public void set(int type, long triggerAtTime, long windowLength, long interval, - PendingIntent operation, WorkSource workSource) { - if (workSource != null) { - mContext.enforceCallingPermission( - android.Manifest.permission.UPDATE_DEVICE_STATS, - "AlarmManager.set"); + void setTimeZoneImpl(String tz) { + if (TextUtils.isEmpty(tz)) { + return; + } + + TimeZone zone = TimeZone.getTimeZone(tz); + // Prevent reentrant calls from stepping on each other when writing + // the time zone property + boolean timeZoneWasChanged = false; + synchronized (this) { + String current = SystemProperties.get(TIMEZONE_PROPERTY); + if (current == null || !current.equals(zone.getID())) { + if (localLOGV) { + Slog.v(TAG, "timezone changed: " + current + ", new=" + zone.getID()); + } + timeZoneWasChanged = true; + SystemProperties.set(TIMEZONE_PROPERTY, zone.getID()); + } + + // Update the kernel timezone information + // Kernel tracks time offsets as 'minutes west of GMT' + int gmtOffset = zone.getOffset(System.currentTimeMillis()); + setKernelTimezone(mDescriptor, -(gmtOffset / 60000)); } - set(type, triggerAtTime, windowLength, interval, operation, false, workSource); + TimeZone.setDefault(null); + + if (timeZoneWasChanged) { + Intent intent = new Intent(Intent.ACTION_TIMEZONE_CHANGED); + intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + intent.putExtra("time-zone", zone.getID()); + getContext().sendBroadcastAsUser(intent, UserHandle.ALL); + } } - public void set(int type, long triggerAtTime, long windowLength, long interval, + void removeImpl(PendingIntent operation) { + if (operation == null) { + return; + } + synchronized (mLock) { + removeLocked(operation); + } + } + + void setImpl(int type, long triggerAtTime, long windowLength, long interval, PendingIntent operation, boolean isStandalone, WorkSource workSource) { if (operation == null) { Slog.w(TAG, "set/setRepeating ignored because there is no intent"); @@ -606,231 +637,64 @@ class AlarmManagerService extends IAlarmManager.Stub { rescheduleKernelAlarmsLocked(); } - private void logBatchesLocked() { - ByteArrayOutputStream bs = new ByteArrayOutputStream(2048); - PrintWriter pw = new PrintWriter(bs); - final long nowRTC = System.currentTimeMillis(); - final long nowELAPSED = SystemClock.elapsedRealtime(); - final int NZ = mAlarmBatches.size(); - for (int iz = 0; iz < NZ; iz++) { - Batch bz = mAlarmBatches.get(iz); - pw.append("Batch "); pw.print(iz); pw.append(": "); pw.println(bz); - dumpAlarmList(pw, bz.alarms, " ", nowELAPSED, nowRTC); - pw.flush(); - Slog.v(TAG, bs.toString()); - bs.reset(); - } - } - - private boolean validateConsistencyLocked() { - if (DEBUG_VALIDATE) { - long lastTime = Long.MIN_VALUE; - final int N = mAlarmBatches.size(); - for (int i = 0; i < N; i++) { - Batch b = mAlarmBatches.get(i); - if (b.start >= lastTime) { - // duplicate start times are okay because of standalone batches - lastTime = b.start; - } else { - Slog.e(TAG, "CONSISTENCY FAILURE: Batch " + i + " is out of order"); - logBatchesLocked(); - return false; - } - } - } - return true; - } - - private Batch findFirstWakeupBatchLocked() { - final int N = mAlarmBatches.size(); - for (int i = 0; i < N; i++) { - Batch b = mAlarmBatches.get(i); - if (b.hasWakeups()) { - return b; + private final IBinder mService = new IAlarmManager.Stub() { + @Override + public void set(int type, long triggerAtTime, long windowLength, long interval, + PendingIntent operation, WorkSource workSource) { + if (workSource != null) { + getContext().enforceCallingPermission( + android.Manifest.permission.UPDATE_DEVICE_STATS, + "AlarmManager.set"); } - } - return null; - } - private void rescheduleKernelAlarmsLocked() { - // Schedule the next upcoming wakeup alarm. If there is a deliverable batch - // prior to that which contains no wakeups, we schedule that as well. - if (mAlarmBatches.size() > 0) { - final Batch firstWakeup = findFirstWakeupBatchLocked(); - final Batch firstBatch = mAlarmBatches.get(0); - if (firstWakeup != null && mNextWakeup != firstWakeup.start) { - mNextWakeup = firstWakeup.start; - setLocked(ELAPSED_REALTIME_WAKEUP, firstWakeup.start); - } - if (firstBatch != firstWakeup && mNextNonWakeup != firstBatch.start) { - mNextNonWakeup = firstBatch.start; - setLocked(ELAPSED_REALTIME, firstBatch.start); - } + setImpl(type, triggerAtTime, windowLength, interval, operation, + false, workSource); } - } - - public void setTime(long millis) { - mContext.enforceCallingOrSelfPermission( - "android.permission.SET_TIME", - "setTime"); - - SystemClock.setCurrentTimeMillis(millis); - } - - public void setTimeZone(String tz) { - mContext.enforceCallingOrSelfPermission( - "android.permission.SET_TIME_ZONE", - "setTimeZone"); - - long oldId = Binder.clearCallingIdentity(); - try { - if (TextUtils.isEmpty(tz)) return; - TimeZone zone = TimeZone.getTimeZone(tz); - // Prevent reentrant calls from stepping on each other when writing - // the time zone property - boolean timeZoneWasChanged = false; - synchronized (this) { - String current = SystemProperties.get(TIMEZONE_PROPERTY); - if (current == null || !current.equals(zone.getID())) { - if (localLOGV) { - Slog.v(TAG, "timezone changed: " + current + ", new=" + zone.getID()); - } - timeZoneWasChanged = true; - SystemProperties.set(TIMEZONE_PROPERTY, zone.getID()); - } - - // Update the kernel timezone information - // Kernel tracks time offsets as 'minutes west of GMT' - int gmtOffset = zone.getOffset(System.currentTimeMillis()); - setKernelTimezone(mDescriptor, -(gmtOffset / 60000)); - } - - TimeZone.setDefault(null); - if (timeZoneWasChanged) { - Intent intent = new Intent(Intent.ACTION_TIMEZONE_CHANGED); - intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); - intent.putExtra("time-zone", zone.getID()); - mContext.sendBroadcastAsUser(intent, UserHandle.ALL); - } - } finally { - Binder.restoreCallingIdentity(oldId); - } - } - - public void remove(PendingIntent operation) { - if (operation == null) { - return; - } - synchronized (mLock) { - removeLocked(operation); - } - } - - public void removeLocked(PendingIntent operation) { - boolean didRemove = false; - for (int i = mAlarmBatches.size() - 1; i >= 0; i--) { - Batch b = mAlarmBatches.get(i); - didRemove |= b.remove(operation); - if (b.size() == 0) { - mAlarmBatches.remove(i); - } - } + @Override + public void setTime(long millis) { + getContext().enforceCallingOrSelfPermission( + "android.permission.SET_TIME", + "setTime"); - if (didRemove) { - if (DEBUG_BATCH) { - Slog.v(TAG, "remove(operation) changed bounds; rebatching"); - } - rebatchAllAlarmsLocked(true); - rescheduleKernelAlarmsLocked(); + SystemClock.setCurrentTimeMillis(millis); } - } - public void removeLocked(String packageName) { - boolean didRemove = false; - for (int i = mAlarmBatches.size() - 1; i >= 0; i--) { - Batch b = mAlarmBatches.get(i); - didRemove |= b.remove(packageName); - if (b.size() == 0) { - mAlarmBatches.remove(i); + @Override + public void setTimeZone(String tz) { + getContext().enforceCallingOrSelfPermission( + "android.permission.SET_TIME_ZONE", + "setTimeZone"); + + final long oldId = Binder.clearCallingIdentity(); + try { + setTimeZoneImpl(tz); + } finally { + Binder.restoreCallingIdentity(oldId); } } - if (didRemove) { - if (DEBUG_BATCH) { - Slog.v(TAG, "remove(package) changed bounds; rebatching"); - } - rebatchAllAlarmsLocked(true); - rescheduleKernelAlarmsLocked(); - } - } + @Override + public void remove(PendingIntent operation) { + removeImpl(operation); - public void removeUserLocked(int userHandle) { - boolean didRemove = false; - for (int i = mAlarmBatches.size() - 1; i >= 0; i--) { - Batch b = mAlarmBatches.get(i); - didRemove |= b.remove(userHandle); - if (b.size() == 0) { - mAlarmBatches.remove(i); - } } - if (didRemove) { - if (DEBUG_BATCH) { - Slog.v(TAG, "remove(user) changed bounds; rebatching"); + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump AlarmManager from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + return; } - rebatchAllAlarmsLocked(true); - rescheduleKernelAlarmsLocked(); - } - } - public boolean lookForPackageLocked(String packageName) { - for (int i = 0; i < mAlarmBatches.size(); i++) { - Batch b = mAlarmBatches.get(i); - if (b.hasPackage(packageName)) { - return true; - } + dumpImpl(pw); } - return false; - } + }; - private void setLocked(int type, long when) - { - if (mDescriptor != -1) - { - // The kernel never triggers alarms with negative wakeup times - // so we ensure they are positive. - long alarmSeconds, alarmNanoseconds; - if (when < 0) { - alarmSeconds = 0; - alarmNanoseconds = 0; - } else { - alarmSeconds = when / 1000; - alarmNanoseconds = (when % 1000) * 1000 * 1000; - } - - set(mDescriptor, type, alarmSeconds, alarmNanoseconds); - } - else - { - Message msg = Message.obtain(); - msg.what = ALARM_EVENT; - - mHandler.removeMessages(ALARM_EVENT); - mHandler.sendMessageAtTime(msg, when); - } - } - - @Override - protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) - != PackageManager.PERMISSION_GRANTED) { - pw.println("Permission Denial: can't dump AlarmManager from from pid=" - + Binder.getCallingPid() - + ", uid=" + Binder.getCallingUid()); - return; - } - + void dumpImpl(PrintWriter pw) { synchronized (mLock) { pw.println("Current Alarm Manager state:"); final long nowRTC = System.currentTimeMillis(); @@ -980,6 +844,159 @@ class AlarmManagerService extends IAlarmManager.Stub { } } + private void logBatchesLocked() { + ByteArrayOutputStream bs = new ByteArrayOutputStream(2048); + PrintWriter pw = new PrintWriter(bs); + final long nowRTC = System.currentTimeMillis(); + final long nowELAPSED = SystemClock.elapsedRealtime(); + final int NZ = mAlarmBatches.size(); + for (int iz = 0; iz < NZ; iz++) { + Batch bz = mAlarmBatches.get(iz); + pw.append("Batch "); pw.print(iz); pw.append(": "); pw.println(bz); + dumpAlarmList(pw, bz.alarms, " ", nowELAPSED, nowRTC); + pw.flush(); + Slog.v(TAG, bs.toString()); + bs.reset(); + } + } + + private boolean validateConsistencyLocked() { + if (DEBUG_VALIDATE) { + long lastTime = Long.MIN_VALUE; + final int N = mAlarmBatches.size(); + for (int i = 0; i < N; i++) { + Batch b = mAlarmBatches.get(i); + if (b.start >= lastTime) { + // duplicate start times are okay because of standalone batches + lastTime = b.start; + } else { + Slog.e(TAG, "CONSISTENCY FAILURE: Batch " + i + " is out of order"); + logBatchesLocked(); + return false; + } + } + } + return true; + } + + private Batch findFirstWakeupBatchLocked() { + final int N = mAlarmBatches.size(); + for (int i = 0; i < N; i++) { + Batch b = mAlarmBatches.get(i); + if (b.hasWakeups()) { + return b; + } + } + return null; + } + + void rescheduleKernelAlarmsLocked() { + // Schedule the next upcoming wakeup alarm. If there is a deliverable batch + // prior to that which contains no wakeups, we schedule that as well. + if (mAlarmBatches.size() > 0) { + final Batch firstWakeup = findFirstWakeupBatchLocked(); + final Batch firstBatch = mAlarmBatches.get(0); + if (firstWakeup != null && mNextWakeup != firstWakeup.start) { + mNextWakeup = firstWakeup.start; + setLocked(ELAPSED_REALTIME_WAKEUP, firstWakeup.start); + } + if (firstBatch != firstWakeup && mNextNonWakeup != firstBatch.start) { + mNextNonWakeup = firstBatch.start; + setLocked(ELAPSED_REALTIME, firstBatch.start); + } + } + } + + private void removeLocked(PendingIntent operation) { + boolean didRemove = false; + for (int i = mAlarmBatches.size() - 1; i >= 0; i--) { + Batch b = mAlarmBatches.get(i); + didRemove |= b.remove(operation); + if (b.size() == 0) { + mAlarmBatches.remove(i); + } + } + + if (didRemove) { + if (DEBUG_BATCH) { + Slog.v(TAG, "remove(operation) changed bounds; rebatching"); + } + rebatchAllAlarmsLocked(true); + rescheduleKernelAlarmsLocked(); + } + } + + void removeLocked(String packageName) { + boolean didRemove = false; + for (int i = mAlarmBatches.size() - 1; i >= 0; i--) { + Batch b = mAlarmBatches.get(i); + didRemove |= b.remove(packageName); + if (b.size() == 0) { + mAlarmBatches.remove(i); + } + } + + if (didRemove) { + if (DEBUG_BATCH) { + Slog.v(TAG, "remove(package) changed bounds; rebatching"); + } + rebatchAllAlarmsLocked(true); + rescheduleKernelAlarmsLocked(); + } + } + + void removeUserLocked(int userHandle) { + boolean didRemove = false; + for (int i = mAlarmBatches.size() - 1; i >= 0; i--) { + Batch b = mAlarmBatches.get(i); + didRemove |= b.remove(userHandle); + if (b.size() == 0) { + mAlarmBatches.remove(i); + } + } + + if (didRemove) { + if (DEBUG_BATCH) { + Slog.v(TAG, "remove(user) changed bounds; rebatching"); + } + rebatchAllAlarmsLocked(true); + rescheduleKernelAlarmsLocked(); + } + } + + boolean lookForPackageLocked(String packageName) { + for (int i = 0; i < mAlarmBatches.size(); i++) { + Batch b = mAlarmBatches.get(i); + if (b.hasPackage(packageName)) { + return true; + } + } + return false; + } + + private void setLocked(int type, long when) { + if (mDescriptor != -1) { + // The kernel never triggers alarms with negative wakeup times + // so we ensure they are positive. + long alarmSeconds, alarmNanoseconds; + if (when < 0) { + alarmSeconds = 0; + alarmNanoseconds = 0; + } else { + alarmSeconds = when / 1000; + alarmNanoseconds = (when % 1000) * 1000 * 1000; + } + + set(mDescriptor, type, alarmSeconds, alarmNanoseconds); + } else { + Message msg = Message.obtain(); + msg.what = ALARM_EVENT; + + mHandler.removeMessages(ALARM_EVENT); + mHandler.sendMessageAtTime(msg, when); + } + } + private static final void dumpAlarmList(PrintWriter pw, ArrayList list, String prefix, String label, long now) { for (int i=list.size()-1; i>=0; i--) { @@ -1020,7 +1037,7 @@ class AlarmManagerService extends IAlarmManager.Stub { private native int waitForAlarm(int fd); private native int setKernelTimezone(int fd, int minuteswest); - private void triggerAlarmsLocked(ArrayList triggerList, long nowELAPSED, long nowRTC) { + void triggerAlarmsLocked(ArrayList triggerList, long nowELAPSED, long nowRTC) { // batches are temporally sorted, so we need only pull from the // start of the list until we either empty it or hit a batch // that is not yet deliverable @@ -1166,13 +1183,13 @@ class AlarmManagerService extends IAlarmManager.Stub { if (DEBUG_BATCH) { Slog.v(TAG, "Time changed notification from kernel; rebatching"); } - remove(mTimeTickSender); + removeImpl(mTimeTickSender); rebatchAllAlarms(); mClockReceiver.scheduleTimeTickEvent(); Intent intent = new Intent(Intent.ACTION_TIME_CHANGED); intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + getContext().sendBroadcastAsUser(intent, UserHandle.ALL); } synchronized (mLock) { @@ -1206,7 +1223,7 @@ class AlarmManagerService extends IAlarmManager.Stub { Alarm alarm = triggerList.get(i); try { if (localLOGV) Slog.v(TAG, "sending alarm " + alarm); - alarm.operation.send(mContext, 0, + alarm.operation.send(getContext(), 0, mBackgroundIntent.putExtra( Intent.EXTRA_ALARM_COUNT, alarm.count), mResultReceiver, mHandler); @@ -1248,7 +1265,7 @@ class AlarmManagerService extends IAlarmManager.Stub { if (alarm.repeatInterval > 0) { // This IntentSender is no longer valid, but this // is a repeating alarm, so toss the hoser. - remove(alarm.operation); + removeImpl(alarm.operation); } } catch (RuntimeException e) { Slog.w(TAG, "Failure sending alarm.", e); @@ -1310,7 +1327,7 @@ class AlarmManagerService extends IAlarmManager.Stub { if (alarm.repeatInterval > 0) { // This IntentSender is no longer valid, but this // is a repeating alarm, so toss the hoser. - remove(alarm.operation); + removeImpl(alarm.operation); } } } @@ -1323,7 +1340,7 @@ class AlarmManagerService extends IAlarmManager.Stub { IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_TIME_TICK); filter.addAction(Intent.ACTION_DATE_CHANGED); - mContext.registerReceiver(this, filter); + getContext().registerReceiver(this, filter); } @Override @@ -1354,7 +1371,7 @@ class AlarmManagerService extends IAlarmManager.Stub { final long tickEventDelay = nextTime - currentTime; final WorkSource workSource = null; // Let system take blame for time tick events. - set(ELAPSED_REALTIME, SystemClock.elapsedRealtime() + tickEventDelay, 0, + setImpl(ELAPSED_REALTIME, SystemClock.elapsedRealtime() + tickEventDelay, 0, 0, mTimeTickSender, true, workSource); } @@ -1368,7 +1385,7 @@ class AlarmManagerService extends IAlarmManager.Stub { calendar.add(Calendar.DAY_OF_MONTH, 1); final WorkSource workSource = null; // Let system take blame for date change events. - set(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender, true, workSource); + setImpl(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender, true, workSource); } } @@ -1379,12 +1396,12 @@ class AlarmManagerService extends IAlarmManager.Stub { filter.addAction(Intent.ACTION_PACKAGE_RESTARTED); filter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART); filter.addDataScheme("package"); - mContext.registerReceiver(this, filter); + getContext().registerReceiver(this, filter); // Register for events related to sdcard installation. IntentFilter sdFilter = new IntentFilter(); sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); sdFilter.addAction(Intent.ACTION_USER_STOPPED); - mContext.registerReceiver(this, sdFilter); + getContext().registerReceiver(this, sdFilter); } @Override diff --git a/services/java/com/android/server/BatteryService.java b/services/java/com/android/server/BatteryService.java index b234a4e55109..6e72e24de330 100644 --- a/services/java/com/android/server/BatteryService.java +++ b/services/java/com/android/server/BatteryService.java @@ -19,6 +19,8 @@ package com.android.server; import android.os.BatteryStats; import com.android.internal.app.IBatteryStats; import com.android.server.am.BatteryStatsService; +import com.android.server.lights.Light; +import com.android.server.lights.LightsManager; import android.app.ActivityManagerNative; import android.content.ContentResolver; @@ -134,13 +136,10 @@ public final class BatteryService extends Binder { private boolean mSentLowBatteryBroadcast = false; - private BatteryListener mBatteryPropertiesListener; - private IBatteryPropertiesRegistrar mBatteryPropertiesRegistrar; - - public BatteryService(Context context, LightsService lights) { + public BatteryService(Context context, LightsManager lightsManager) { mContext = context; mHandler = new Handler(true /*async*/); - mLed = new Led(context, lights); + mLed = new Led(context, lightsManager); mBatteryStats = BatteryStatsService.getService(); mCriticalBatteryLevel = mContext.getResources().getInteger( @@ -158,13 +157,11 @@ public final class BatteryService extends Binder { "DEVPATH=/devices/virtual/switch/invalid_charger"); } - mBatteryPropertiesListener = new BatteryListener(); - IBinder b = ServiceManager.getService("batteryproperties"); - mBatteryPropertiesRegistrar = IBatteryPropertiesRegistrar.Stub.asInterface(b); - + final IBatteryPropertiesRegistrar batteryPropertiesRegistrar = + IBatteryPropertiesRegistrar.Stub.asInterface(b); try { - mBatteryPropertiesRegistrar.registerListener(mBatteryPropertiesListener); + batteryPropertiesRegistrar.registerListener(new BatteryListener()); } catch (RemoteException e) { // Should never happen. } @@ -677,7 +674,7 @@ public final class BatteryService extends Binder { }; private final class Led { - private final LightsService.Light mBatteryLight; + private final Light mBatteryLight; private final int mBatteryLowARGB; private final int mBatteryMediumARGB; @@ -685,8 +682,8 @@ public final class BatteryService extends Binder { private final int mBatteryLedOn; private final int mBatteryLedOff; - public Led(Context context, LightsService lights) { - mBatteryLight = lights.getLight(LightsService.LIGHT_ID_BATTERY); + public Led(Context context, LightsManager lights) { + mBatteryLight = lights.getLight(LightsManager.LIGHT_ID_BATTERY); mBatteryLowARGB = context.getResources().getInteger( com.android.internal.R.integer.config_notificationsBatteryLowARGB); @@ -712,7 +709,7 @@ public final class BatteryService extends Binder { mBatteryLight.setColor(mBatteryLowARGB); } else { // Flash red when battery is low and not charging - mBatteryLight.setFlashing(mBatteryLowARGB, LightsService.LIGHT_FLASH_TIMED, + mBatteryLight.setFlashing(mBatteryLowARGB, Light.LIGHT_FLASH_TIMED, mBatteryLedOn, mBatteryLedOff); } } else if (status == BatteryManager.BATTERY_STATUS_CHARGING @@ -732,8 +729,14 @@ public final class BatteryService extends Binder { } private final class BatteryListener extends IBatteryPropertiesListener.Stub { + @Override public void batteryPropertiesChanged(BatteryProperties props) { - BatteryService.this.update(props); + final long identity = Binder.clearCallingIdentity(); + try { + BatteryService.this.update(props); + } finally { + Binder.restoreCallingIdentity(identity); + } } } } diff --git a/services/java/com/android/server/DeviceStorageMonitorService.java b/services/java/com/android/server/DeviceStorageMonitorService.java deleted file mode 100644 index 016c561077dc..000000000000 --- a/services/java/com/android/server/DeviceStorageMonitorService.java +++ /dev/null @@ -1,491 +0,0 @@ -/* - * Copyright (C) 2007-2008 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 android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.pm.IPackageDataObserver; -import android.content.pm.IPackageManager; -import android.content.pm.PackageManager; -import android.os.Binder; -import android.os.Environment; -import android.os.FileObserver; -import android.os.Handler; -import android.os.Message; -import android.os.Process; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.StatFs; -import android.os.SystemClock; -import android.os.SystemProperties; -import android.os.UserHandle; -import android.os.storage.StorageManager; -import android.provider.Settings; -import android.text.format.Formatter; -import android.util.EventLog; -import android.util.Slog; -import android.util.TimeUtils; - -import java.io.File; -import java.io.FileDescriptor; -import java.io.PrintWriter; - -/** - * This class implements a service to monitor the amount of disk - * storage space on the device. If the free storage on device is less - * than a tunable threshold value (a secure settings parameter; - * default 10%) a low memory notification is displayed to alert the - * user. If the user clicks on the low memory notification the - * Application Manager application gets launched to let the user free - * storage space. - * - * Event log events: A low memory event with the free storage on - * device in bytes is logged to the event log when the device goes low - * on storage space. The amount of free storage on the device is - * periodically logged to the event log. The log interval is a secure - * settings parameter with a default value of 12 hours. When the free - * storage differential goes below a threshold (again a secure - * settings parameter with a default value of 2MB), the free memory is - * logged to the event log. - */ -public class DeviceStorageMonitorService extends Binder { - private static final String TAG = "DeviceStorageMonitorService"; - - private static final boolean DEBUG = false; - private static final boolean localLOGV = false; - - private static final int DEVICE_MEMORY_WHAT = 1; - private static final int MONITOR_INTERVAL = 1; //in minutes - private static final int LOW_MEMORY_NOTIFICATION_ID = 1; - - private static final int DEFAULT_FREE_STORAGE_LOG_INTERVAL_IN_MINUTES = 12*60; //in minutes - private static final long DEFAULT_DISK_FREE_CHANGE_REPORTING_THRESHOLD = 2 * 1024 * 1024; // 2MB - private static final long DEFAULT_CHECK_INTERVAL = MONITOR_INTERVAL*60*1000; - - private long mFreeMem; // on /data - private long mFreeMemAfterLastCacheClear; // on /data - private long mLastReportedFreeMem; - private long mLastReportedFreeMemTime; - private boolean mLowMemFlag=false; - private boolean mMemFullFlag=false; - private Context mContext; - private ContentResolver mResolver; - private long mTotalMemory; // on /data - private StatFs mDataFileStats; - private StatFs mSystemFileStats; - private StatFs mCacheFileStats; - - private static final File DATA_PATH = Environment.getDataDirectory(); - private static final File SYSTEM_PATH = Environment.getRootDirectory(); - private static final File CACHE_PATH = Environment.getDownloadCacheDirectory(); - - private long mThreadStartTime = -1; - private boolean mClearSucceeded = false; - private boolean mClearingCache; - private Intent mStorageLowIntent; - private Intent mStorageOkIntent; - private Intent mStorageFullIntent; - private Intent mStorageNotFullIntent; - private CachePackageDataObserver mClearCacheObserver; - private final CacheFileDeletedObserver mCacheFileDeletedObserver; - private static final int _TRUE = 1; - private static final int _FALSE = 0; - // This is the raw threshold that has been set at which we consider - // storage to be low. - private long mMemLowThreshold; - // This is the threshold at which we start trying to flush caches - // to get below the low threshold limit. It is less than the low - // threshold; we will allow storage to get a bit beyond the limit - // before flushing and checking if we are actually low. - private long mMemCacheStartTrimThreshold; - // This is the threshold that we try to get to when deleting cache - // files. This is greater than the low threshold so that we will flush - // more files than absolutely needed, to reduce the frequency that - // flushing takes place. - private long mMemCacheTrimToThreshold; - private long mMemFullThreshold; - - /** - * This string is used for ServiceManager access to this class. - */ - public static final String SERVICE = "devicestoragemonitor"; - - /** - * Handler that checks the amount of disk space on the device and sends a - * notification if the device runs low on disk space - */ - Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - //don't handle an invalid message - if (msg.what != DEVICE_MEMORY_WHAT) { - Slog.e(TAG, "Will not process invalid message"); - return; - } - checkMemory(msg.arg1 == _TRUE); - } - }; - - class CachePackageDataObserver extends IPackageDataObserver.Stub { - public void onRemoveCompleted(String packageName, boolean succeeded) { - mClearSucceeded = succeeded; - mClearingCache = false; - if(localLOGV) Slog.i(TAG, " Clear succeeded:"+mClearSucceeded - +", mClearingCache:"+mClearingCache+" Forcing memory check"); - postCheckMemoryMsg(false, 0); - } - } - - private final void restatDataDir() { - try { - mDataFileStats.restat(DATA_PATH.getAbsolutePath()); - mFreeMem = (long) mDataFileStats.getAvailableBlocks() * - mDataFileStats.getBlockSize(); - } catch (IllegalArgumentException e) { - // use the old value of mFreeMem - } - // Allow freemem to be overridden by debug.freemem for testing - String debugFreeMem = SystemProperties.get("debug.freemem"); - if (!"".equals(debugFreeMem)) { - mFreeMem = Long.parseLong(debugFreeMem); - } - // Read the log interval from secure settings - long freeMemLogInterval = Settings.Global.getLong(mResolver, - Settings.Global.SYS_FREE_STORAGE_LOG_INTERVAL, - DEFAULT_FREE_STORAGE_LOG_INTERVAL_IN_MINUTES)*60*1000; - //log the amount of free memory in event log - long currTime = SystemClock.elapsedRealtime(); - if((mLastReportedFreeMemTime == 0) || - (currTime-mLastReportedFreeMemTime) >= freeMemLogInterval) { - mLastReportedFreeMemTime = currTime; - long mFreeSystem = -1, mFreeCache = -1; - try { - mSystemFileStats.restat(SYSTEM_PATH.getAbsolutePath()); - mFreeSystem = (long) mSystemFileStats.getAvailableBlocks() * - mSystemFileStats.getBlockSize(); - } catch (IllegalArgumentException e) { - // ignore; report -1 - } - try { - mCacheFileStats.restat(CACHE_PATH.getAbsolutePath()); - mFreeCache = (long) mCacheFileStats.getAvailableBlocks() * - mCacheFileStats.getBlockSize(); - } catch (IllegalArgumentException e) { - // ignore; report -1 - } - EventLog.writeEvent(EventLogTags.FREE_STORAGE_LEFT, - mFreeMem, mFreeSystem, mFreeCache); - } - // Read the reporting threshold from secure settings - long threshold = Settings.Global.getLong(mResolver, - Settings.Global.DISK_FREE_CHANGE_REPORTING_THRESHOLD, - DEFAULT_DISK_FREE_CHANGE_REPORTING_THRESHOLD); - // If mFree changed significantly log the new value - long delta = mFreeMem - mLastReportedFreeMem; - if (delta > threshold || delta < -threshold) { - mLastReportedFreeMem = mFreeMem; - EventLog.writeEvent(EventLogTags.FREE_STORAGE_CHANGED, mFreeMem); - } - } - - private final void clearCache() { - if (mClearCacheObserver == null) { - // Lazy instantiation - mClearCacheObserver = new CachePackageDataObserver(); - } - mClearingCache = true; - try { - if (localLOGV) Slog.i(TAG, "Clearing cache"); - IPackageManager.Stub.asInterface(ServiceManager.getService("package")). - freeStorageAndNotify(mMemCacheTrimToThreshold, mClearCacheObserver); - } catch (RemoteException e) { - Slog.w(TAG, "Failed to get handle for PackageManger Exception: "+e); - mClearingCache = false; - mClearSucceeded = false; - } - } - - private final void checkMemory(boolean checkCache) { - //if the thread that was started to clear cache is still running do nothing till its - //finished clearing cache. Ideally this flag could be modified by clearCache - // and should be accessed via a lock but even if it does this test will fail now and - //hopefully the next time this flag will be set to the correct value. - if(mClearingCache) { - if(localLOGV) Slog.i(TAG, "Thread already running just skip"); - //make sure the thread is not hung for too long - long diffTime = System.currentTimeMillis() - mThreadStartTime; - if(diffTime > (10*60*1000)) { - Slog.w(TAG, "Thread that clears cache file seems to run for ever"); - } - } else { - restatDataDir(); - if (localLOGV) Slog.v(TAG, "freeMemory="+mFreeMem); - - //post intent to NotificationManager to display icon if necessary - if (mFreeMem < mMemLowThreshold) { - if (checkCache) { - // We are allowed to clear cache files at this point to - // try to get down below the limit, because this is not - // the initial call after a cache clear has been attempted. - // In this case we will try a cache clear if our free - // space has gone below the cache clear limit. - if (mFreeMem < mMemCacheStartTrimThreshold) { - // We only clear the cache if the free storage has changed - // a significant amount since the last time. - if ((mFreeMemAfterLastCacheClear-mFreeMem) - >= ((mMemLowThreshold-mMemCacheStartTrimThreshold)/4)) { - // See if clearing cache helps - // Note that clearing cache is asynchronous and so we do a - // memory check again once the cache has been cleared. - mThreadStartTime = System.currentTimeMillis(); - mClearSucceeded = false; - clearCache(); - } - } - } else { - // This is a call from after clearing the cache. Note - // the amount of free storage at this point. - mFreeMemAfterLastCacheClear = mFreeMem; - if (!mLowMemFlag) { - // We tried to clear the cache, but that didn't get us - // below the low storage limit. Tell the user. - Slog.i(TAG, "Running low on memory. Sending notification"); - sendNotification(); - mLowMemFlag = true; - } else { - if (localLOGV) Slog.v(TAG, "Running low on memory " + - "notification already sent. do nothing"); - } - } - } else { - mFreeMemAfterLastCacheClear = mFreeMem; - if (mLowMemFlag) { - Slog.i(TAG, "Memory available. Cancelling notification"); - cancelNotification(); - mLowMemFlag = false; - } - } - if (mFreeMem < mMemFullThreshold) { - if (!mMemFullFlag) { - sendFullNotification(); - mMemFullFlag = true; - } - } else { - if (mMemFullFlag) { - cancelFullNotification(); - mMemFullFlag = false; - } - } - } - if(localLOGV) Slog.i(TAG, "Posting Message again"); - //keep posting messages to itself periodically - postCheckMemoryMsg(true, DEFAULT_CHECK_INTERVAL); - } - - private void postCheckMemoryMsg(boolean clearCache, long delay) { - // Remove queued messages - mHandler.removeMessages(DEVICE_MEMORY_WHAT); - mHandler.sendMessageDelayed(mHandler.obtainMessage(DEVICE_MEMORY_WHAT, - clearCache ?_TRUE : _FALSE, 0), - delay); - } - - /** - * Constructor to run service. initializes the disk space threshold value - * and posts an empty message to kickstart the process. - */ - public DeviceStorageMonitorService(Context context) { - mLastReportedFreeMemTime = 0; - mContext = context; - mResolver = mContext.getContentResolver(); - //create StatFs object - mDataFileStats = new StatFs(DATA_PATH.getAbsolutePath()); - mSystemFileStats = new StatFs(SYSTEM_PATH.getAbsolutePath()); - mCacheFileStats = new StatFs(CACHE_PATH.getAbsolutePath()); - //initialize total storage on device - mTotalMemory = (long)mDataFileStats.getBlockCount() * - mDataFileStats.getBlockSize(); - mStorageLowIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_LOW); - mStorageLowIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mStorageOkIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_OK); - mStorageOkIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mStorageFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_FULL); - mStorageFullIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mStorageNotFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_NOT_FULL); - mStorageNotFullIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - - // cache storage thresholds - final StorageManager sm = StorageManager.from(context); - mMemLowThreshold = sm.getStorageLowBytes(DATA_PATH); - mMemFullThreshold = sm.getStorageFullBytes(DATA_PATH); - - mMemCacheStartTrimThreshold = ((mMemLowThreshold*3)+mMemFullThreshold)/4; - mMemCacheTrimToThreshold = mMemLowThreshold - + ((mMemLowThreshold-mMemCacheStartTrimThreshold)*2); - mFreeMemAfterLastCacheClear = mTotalMemory; - checkMemory(true); - - mCacheFileDeletedObserver = new CacheFileDeletedObserver(); - mCacheFileDeletedObserver.startWatching(); - } - - /** - * This method sends a notification to NotificationManager to display - * an error dialog indicating low disk space and launch the Installer - * application - */ - private final void sendNotification() { - if(localLOGV) Slog.i(TAG, "Sending low memory notification"); - //log the event to event log with the amount of free storage(in bytes) left on the device - EventLog.writeEvent(EventLogTags.LOW_STORAGE, mFreeMem); - // Pack up the values and broadcast them to everyone - Intent lowMemIntent = new Intent(Environment.isExternalStorageEmulated() - ? Settings.ACTION_INTERNAL_STORAGE_SETTINGS - : Intent.ACTION_MANAGE_PACKAGE_STORAGE); - lowMemIntent.putExtra("memory", mFreeMem); - lowMemIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - NotificationManager mNotificationMgr = - (NotificationManager)mContext.getSystemService( - Context.NOTIFICATION_SERVICE); - CharSequence title = mContext.getText( - com.android.internal.R.string.low_internal_storage_view_title); - CharSequence details = mContext.getText( - com.android.internal.R.string.low_internal_storage_view_text); - PendingIntent intent = PendingIntent.getActivityAsUser(mContext, 0, lowMemIntent, 0, - null, UserHandle.CURRENT); - Notification notification = new Notification(); - notification.icon = com.android.internal.R.drawable.stat_notify_disk_full; - notification.tickerText = title; - notification.flags |= Notification.FLAG_NO_CLEAR; - notification.setLatestEventInfo(mContext, title, details, intent); - mNotificationMgr.notifyAsUser(null, LOW_MEMORY_NOTIFICATION_ID, notification, - UserHandle.ALL); - mContext.sendStickyBroadcastAsUser(mStorageLowIntent, UserHandle.ALL); - } - - /** - * Cancels low storage notification and sends OK intent. - */ - private final void cancelNotification() { - if(localLOGV) Slog.i(TAG, "Canceling low memory notification"); - NotificationManager mNotificationMgr = - (NotificationManager)mContext.getSystemService( - Context.NOTIFICATION_SERVICE); - //cancel notification since memory has been freed - mNotificationMgr.cancelAsUser(null, LOW_MEMORY_NOTIFICATION_ID, UserHandle.ALL); - - mContext.removeStickyBroadcastAsUser(mStorageLowIntent, UserHandle.ALL); - mContext.sendBroadcastAsUser(mStorageOkIntent, UserHandle.ALL); - } - - /** - * Send a notification when storage is full. - */ - private final void sendFullNotification() { - if(localLOGV) Slog.i(TAG, "Sending memory full notification"); - mContext.sendStickyBroadcastAsUser(mStorageFullIntent, UserHandle.ALL); - } - - /** - * Cancels memory full notification and sends "not full" intent. - */ - private final void cancelFullNotification() { - if(localLOGV) Slog.i(TAG, "Canceling memory full notification"); - mContext.removeStickyBroadcastAsUser(mStorageFullIntent, UserHandle.ALL); - mContext.sendBroadcastAsUser(mStorageNotFullIntent, UserHandle.ALL); - } - - public void updateMemory() { - int callingUid = getCallingUid(); - if(callingUid != Process.SYSTEM_UID) { - return; - } - // force an early check - postCheckMemoryMsg(true, 0); - } - - /** - * Callable from other things in the system service to obtain the low memory - * threshold. - * - * @return low memory threshold in bytes - */ - public long getMemoryLowThreshold() { - return mMemLowThreshold; - } - - /** - * Callable from other things in the system process to check whether memory - * is low. - * - * @return true is memory is low - */ - public boolean isMemoryLow() { - return mLowMemFlag; - } - - public static class CacheFileDeletedObserver extends FileObserver { - public CacheFileDeletedObserver() { - super(Environment.getDownloadCacheDirectory().getAbsolutePath(), FileObserver.DELETE); - } - - @Override - public void onEvent(int event, String path) { - EventLogTags.writeCacheFileDeleted(path); - } - } - - @Override - protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) - != PackageManager.PERMISSION_GRANTED) { - - pw.println("Permission Denial: can't dump " + SERVICE + " from from pid=" - + Binder.getCallingPid() - + ", uid=" + Binder.getCallingUid()); - return; - } - - pw.println("Current DeviceStorageMonitor state:"); - pw.print(" mFreeMem="); pw.print(Formatter.formatFileSize(mContext, mFreeMem)); - pw.print(" mTotalMemory="); - pw.println(Formatter.formatFileSize(mContext, mTotalMemory)); - pw.print(" mFreeMemAfterLastCacheClear="); - pw.println(Formatter.formatFileSize(mContext, mFreeMemAfterLastCacheClear)); - pw.print(" mLastReportedFreeMem="); - pw.print(Formatter.formatFileSize(mContext, mLastReportedFreeMem)); - pw.print(" mLastReportedFreeMemTime="); - TimeUtils.formatDuration(mLastReportedFreeMemTime, SystemClock.elapsedRealtime(), pw); - pw.println(); - pw.print(" mLowMemFlag="); pw.print(mLowMemFlag); - pw.print(" mMemFullFlag="); pw.println(mMemFullFlag); - pw.print(" mClearSucceeded="); pw.print(mClearSucceeded); - pw.print(" mClearingCache="); pw.println(mClearingCache); - pw.print(" mMemLowThreshold="); - pw.print(Formatter.formatFileSize(mContext, mMemLowThreshold)); - pw.print(" mMemFullThreshold="); - pw.println(Formatter.formatFileSize(mContext, mMemFullThreshold)); - pw.print(" mMemCacheStartTrimThreshold="); - pw.print(Formatter.formatFileSize(mContext, mMemCacheStartTrimThreshold)); - pw.print(" mMemCacheTrimToThreshold="); - pw.println(Formatter.formatFileSize(mContext, mMemCacheTrimToThreshold)); - } -} diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index f0611493de30..0b3eb1275da2 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -30,7 +30,7 @@ import com.android.internal.view.IInputMethodClient; import com.android.internal.view.IInputMethodManager; import com.android.internal.view.IInputMethodSession; import com.android.internal.view.InputBindResult; -import com.android.server.EventLogTags; +import com.android.server.statusbar.StatusBarManagerService; import com.android.server.wm.WindowManagerService; import org.xmlpull.v1.XmlPullParser; diff --git a/services/java/com/android/server/LightsService.java b/services/java/com/android/server/LightsService.java deleted file mode 100644 index 89bfcacd8cac..000000000000 --- a/services/java/com/android/server/LightsService.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (C) 2008 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 android.content.Context; -import android.content.pm.PackageManager; -import android.os.Handler; -import android.os.IHardwareService; -import android.os.ServiceManager; -import android.os.Message; -import android.util.Slog; - -import java.io.FileInputStream; -import java.io.FileOutputStream; - -public class LightsService { - private static final String TAG = "LightsService"; - private static final boolean DEBUG = false; - - public static final int LIGHT_ID_BACKLIGHT = 0; - public static final int LIGHT_ID_KEYBOARD = 1; - public static final int LIGHT_ID_BUTTONS = 2; - public static final int LIGHT_ID_BATTERY = 3; - public static final int LIGHT_ID_NOTIFICATIONS = 4; - public static final int LIGHT_ID_ATTENTION = 5; - public static final int LIGHT_ID_BLUETOOTH = 6; - public static final int LIGHT_ID_WIFI = 7; - public static final int LIGHT_ID_COUNT = 8; - - public static final int LIGHT_FLASH_NONE = 0; - public static final int LIGHT_FLASH_TIMED = 1; - public static final int LIGHT_FLASH_HARDWARE = 2; - - /** - * Light brightness is managed by a user setting. - */ - public static final int BRIGHTNESS_MODE_USER = 0; - - /** - * Light brightness is managed by a light sensor. - */ - public static final int BRIGHTNESS_MODE_SENSOR = 1; - - private final Light mLights[] = new Light[LIGHT_ID_COUNT]; - - public final class Light { - - private Light(int id) { - mId = id; - } - - public void setBrightness(int brightness) { - setBrightness(brightness, BRIGHTNESS_MODE_USER); - } - - public void setBrightness(int brightness, int brightnessMode) { - synchronized (this) { - int color = brightness & 0x000000ff; - color = 0xff000000 | (color << 16) | (color << 8) | color; - setLightLocked(color, LIGHT_FLASH_NONE, 0, 0, brightnessMode); - } - } - - public void setColor(int color) { - synchronized (this) { - setLightLocked(color, LIGHT_FLASH_NONE, 0, 0, 0); - } - } - - public void setFlashing(int color, int mode, int onMS, int offMS) { - synchronized (this) { - setLightLocked(color, mode, onMS, offMS, BRIGHTNESS_MODE_USER); - } - } - - - public void pulse() { - pulse(0x00ffffff, 7); - } - - public void pulse(int color, int onMS) { - synchronized (this) { - if (mColor == 0 && !mFlashing) { - setLightLocked(color, LIGHT_FLASH_HARDWARE, onMS, 1000, BRIGHTNESS_MODE_USER); - mH.sendMessageDelayed(Message.obtain(mH, 1, this), onMS); - } - } - } - - public void turnOff() { - synchronized (this) { - setLightLocked(0, LIGHT_FLASH_NONE, 0, 0, 0); - } - } - - private void stopFlashing() { - synchronized (this) { - setLightLocked(mColor, LIGHT_FLASH_NONE, 0, 0, BRIGHTNESS_MODE_USER); - } - } - - private void setLightLocked(int color, int mode, int onMS, int offMS, int brightnessMode) { - if (color != mColor || mode != mMode || onMS != mOnMS || offMS != mOffMS) { - if (DEBUG) Slog.v(TAG, "setLight #" + mId + ": color=#" - + Integer.toHexString(color)); - mColor = color; - mMode = mode; - mOnMS = onMS; - mOffMS = offMS; - setLight_native(mNativePointer, mId, color, mode, onMS, offMS, brightnessMode); - } - } - - private int mId; - private int mColor; - private int mMode; - private int mOnMS; - private int mOffMS; - private boolean mFlashing; - } - - /* This class implements an obsolete API that was removed after eclair and re-added during the - * final moments of the froyo release to support flashlight apps that had been using the private - * IHardwareService API. This is expected to go away in the next release. - */ - private final IHardwareService.Stub mLegacyFlashlightHack = new IHardwareService.Stub() { - - private static final String FLASHLIGHT_FILE = "/sys/class/leds/spotlight/brightness"; - - public boolean getFlashlightEnabled() { - try { - FileInputStream fis = new FileInputStream(FLASHLIGHT_FILE); - int result = fis.read(); - fis.close(); - return (result != '0'); - } catch (Exception e) { - return false; - } - } - - public void setFlashlightEnabled(boolean on) { - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FLASHLIGHT) - != PackageManager.PERMISSION_GRANTED && - mContext.checkCallingOrSelfPermission(android.Manifest.permission.HARDWARE_TEST) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Requires FLASHLIGHT or HARDWARE_TEST permission"); - } - try { - FileOutputStream fos = new FileOutputStream(FLASHLIGHT_FILE); - byte[] bytes = new byte[2]; - bytes[0] = (byte)(on ? '1' : '0'); - bytes[1] = '\n'; - fos.write(bytes); - fos.close(); - } catch (Exception e) { - // fail silently - } - } - }; - - LightsService(Context context) { - - mNativePointer = init_native(); - mContext = context; - - ServiceManager.addService("hardware", mLegacyFlashlightHack); - - for (int i = 0; i < LIGHT_ID_COUNT; i++) { - mLights[i] = new Light(i); - } - } - - protected void finalize() throws Throwable { - finalize_native(mNativePointer); - super.finalize(); - } - - public Light getLight(int id) { - return mLights[id]; - } - - private Handler mH = new Handler() { - @Override - public void handleMessage(Message msg) { - Light light = (Light)msg.obj; - light.stopFlashing(); - } - }; - - private static native int init_native(); - private static native void finalize_native(int ptr); - - private static native void setLight_native(int ptr, int light, int color, int mode, - int onMS, int offMS, int brightnessMode); - - private final Context mContext; - - private int mNativePointer; -} diff --git a/services/java/com/android/server/LocalServices.java b/services/java/com/android/server/LocalServices.java new file mode 100644 index 000000000000..deff79dccc72 --- /dev/null +++ b/services/java/com/android/server/LocalServices.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2013 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 android.util.ArrayMap; + +/** + * This class is used in a similar way as ServiceManager, except the services registered here + * are not Binder objects and are only available in the same process. + * + * Once all services are converted to the SystemService interface, this class can be absorbed + * into SystemServiceManager. + */ +public final class LocalServices { + private LocalServices() {} + + private static final ArrayMap, Object> sLocalServiceObjects = + new ArrayMap, Object>(); + + /** + * Returns a local service instance that implements the specified interface. + * + * @param type The type of service. + * @return The service object. + */ + @SuppressWarnings("unchecked") + public static T getService(Class type) { + synchronized (sLocalServiceObjects) { + return (T) sLocalServiceObjects.get(type); + } + } + + /** + * Adds a service instance of the specified interface to the global registry of local services. + */ + public static void addService(Class type, T service) { + synchronized (sLocalServiceObjects) { + if (sLocalServiceObjects.containsKey(type)) { + throw new IllegalStateException("Overriding service registration"); + } + sLocalServiceObjects.put(type, service); + } + } +} diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java deleted file mode 100644 index 04386759df50..000000000000 --- a/services/java/com/android/server/NotificationManagerService.java +++ /dev/null @@ -1,2368 +0,0 @@ -/* - * Copyright (C) 2007 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 static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; -import static org.xmlpull.v1.XmlPullParser.END_TAG; -import static org.xmlpull.v1.XmlPullParser.START_TAG; - -import android.app.ActivityManager; -import android.app.ActivityManagerNative; -import android.app.AppGlobals; -import android.app.AppOpsManager; -import android.app.IActivityManager; -import android.app.INotificationManager; -import android.app.ITransientNotification; -import android.app.Notification; -import android.app.PendingIntent; -import android.app.StatusBarManager; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.ServiceConnection; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.res.Resources; -import android.database.ContentObserver; -import android.graphics.Bitmap; -import android.media.AudioManager; -import android.media.IAudioService; -import android.media.IRingtonePlayer; -import android.net.Uri; -import android.os.Binder; -import android.os.Handler; -import android.os.IBinder; -import android.os.Message; -import android.os.Process; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.UserHandle; -import android.os.UserManager; -import android.os.Vibrator; -import android.provider.Settings; -import android.service.notification.INotificationListener; -import android.service.notification.NotificationListenerService; -import android.service.notification.StatusBarNotification; -import android.telephony.TelephonyManager; -import android.text.TextUtils; -import android.util.AtomicFile; -import android.util.EventLog; -import android.util.Log; -import android.util.Slog; -import android.util.Xml; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; -import android.widget.Toast; - -import com.android.internal.R; - -import com.android.internal.notification.NotificationScorer; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.File; -import java.io.FileDescriptor; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.PrintWriter; -import java.lang.reflect.Array; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.Set; - -import libcore.io.IoUtils; - - -/** {@hide} */ -public class NotificationManagerService extends INotificationManager.Stub -{ - private static final String TAG = "NotificationService"; - private static final boolean DBG = false; - - private static final int MAX_PACKAGE_NOTIFICATIONS = 50; - - // message codes - private static final int MESSAGE_TIMEOUT = 2; - - private static final int LONG_DELAY = 3500; // 3.5 seconds - private static final int SHORT_DELAY = 2000; // 2 seconds - - private static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250}; - private static final int VIBRATE_PATTERN_MAXLEN = 8 * 2 + 1; // up to eight bumps - - private static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_NOTIFICATION; - private static final boolean SCORE_ONGOING_HIGHER = false; - - private static final int JUNK_SCORE = -1000; - private static final int NOTIFICATION_PRIORITY_MULTIPLIER = 10; - private static final int SCORE_DISPLAY_THRESHOLD = Notification.PRIORITY_MIN * NOTIFICATION_PRIORITY_MULTIPLIER; - - // Notifications with scores below this will not interrupt the user, either via LED or - // sound or vibration - private static final int SCORE_INTERRUPTION_THRESHOLD = - Notification.PRIORITY_LOW * NOTIFICATION_PRIORITY_MULTIPLIER; - - private static final boolean ENABLE_BLOCKED_NOTIFICATIONS = true; - private static final boolean ENABLE_BLOCKED_TOASTS = true; - - private static final String ENABLED_NOTIFICATION_LISTENERS_SEPARATOR = ":"; - - final Context mContext; - final IActivityManager mAm; - final UserManager mUserManager; - final IBinder mForegroundToken = new Binder(); - - private WorkerHandler mHandler; - private StatusBarManagerService mStatusBar; - private LightsService.Light mNotificationLight; - private LightsService.Light mAttentionLight; - - private int mDefaultNotificationColor; - private int mDefaultNotificationLedOn; - private int mDefaultNotificationLedOff; - - private long[] mDefaultVibrationPattern; - private long[] mFallbackVibrationPattern; - - private boolean mSystemReady; - private int mDisabledNotifications; - - private NotificationRecord mSoundNotification; - private NotificationRecord mVibrateNotification; - - private IAudioService mAudioService; - private Vibrator mVibrator; - - // for enabling and disabling notification pulse behavior - private boolean mScreenOn = true; - private boolean mInCall = false; - private boolean mNotificationPulseEnabled; - - // used as a mutex for access to all active notifications & listeners - private final ArrayList mNotificationList = - new ArrayList(); - - private ArrayList mToastQueue; - - private ArrayList mLights = new ArrayList(); - private NotificationRecord mLedNotification; - - private final AppOpsManager mAppOps; - - // contains connections to all connected listeners, including app services - // and system listeners - private ArrayList mListeners - = new ArrayList(); - // things that will be put into mListeners as soon as they're ready - private ArrayList mServicesBinding = new ArrayList(); - // lists the component names of all enabled (and therefore connected) listener - // app services for the current user only - private HashSet mEnabledListenersForCurrentUser - = new HashSet(); - // Just the packages from mEnabledListenersForCurrentUser - private HashSet mEnabledListenerPackageNames = new HashSet(); - - // Notification control database. For now just contains disabled packages. - private AtomicFile mPolicyFile; - private HashSet mBlockedPackages = new HashSet(); - - private static final int DB_VERSION = 1; - - private static final String TAG_BODY = "notification-policy"; - private static final String ATTR_VERSION = "version"; - - private static final String TAG_BLOCKED_PKGS = "blocked-packages"; - private static final String TAG_PACKAGE = "package"; - private static final String ATTR_NAME = "name"; - - private final ArrayList mScorers = new ArrayList(); - - private class NotificationListenerInfo implements DeathRecipient { - INotificationListener listener; - ComponentName component; - int userid; - boolean isSystem; - ServiceConnection connection; - - public NotificationListenerInfo(INotificationListener listener, ComponentName component, - int userid, boolean isSystem) { - this.listener = listener; - this.component = component; - this.userid = userid; - this.isSystem = isSystem; - this.connection = null; - } - - public NotificationListenerInfo(INotificationListener listener, ComponentName component, - int userid, ServiceConnection connection) { - this.listener = listener; - this.component = component; - this.userid = userid; - this.isSystem = false; - this.connection = connection; - } - - boolean enabledAndUserMatches(StatusBarNotification sbn) { - final int nid = sbn.getUserId(); - if (!isEnabledForCurrentUser()) { - return false; - } - if (this.userid == UserHandle.USER_ALL) return true; - return (nid == UserHandle.USER_ALL || nid == this.userid); - } - - public void notifyPostedIfUserMatch(StatusBarNotification sbn) { - if (!enabledAndUserMatches(sbn)) { - return; - } - try { - listener.onNotificationPosted(sbn); - } catch (RemoteException ex) { - Log.e(TAG, "unable to notify listener (posted): " + listener, ex); - } - } - - public void notifyRemovedIfUserMatch(StatusBarNotification sbn) { - if (!enabledAndUserMatches(sbn)) return; - try { - listener.onNotificationRemoved(sbn); - } catch (RemoteException ex) { - Log.e(TAG, "unable to notify listener (removed): " + listener, ex); - } - } - - @Override - public void binderDied() { - if (connection == null) { - // This is not a service; it won't be recreated. We can give up this connection. - unregisterListener(this.listener, this.userid); - } - } - - /** convenience method for looking in mEnabledListenersForCurrentUser */ - public boolean isEnabledForCurrentUser() { - if (this.isSystem) return true; - if (this.connection == null) return false; - return mEnabledListenersForCurrentUser.contains(this.component); - } - } - - private static class Archive { - static final int BUFFER_SIZE = 250; - ArrayDeque mBuffer = new ArrayDeque(BUFFER_SIZE); - - public Archive() { - } - - public String toString() { - final StringBuilder sb = new StringBuilder(); - final int N = mBuffer.size(); - sb.append("Archive ("); - sb.append(N); - sb.append(" notification"); - sb.append((N==1)?")":"s)"); - return sb.toString(); - } - - public void record(StatusBarNotification nr) { - if (mBuffer.size() == BUFFER_SIZE) { - mBuffer.removeFirst(); - } - - // We don't want to store the heavy bits of the notification in the archive, - // but other clients in the system process might be using the object, so we - // store a (lightened) copy. - mBuffer.addLast(nr.cloneLight()); - } - - - public void clear() { - mBuffer.clear(); - } - - public Iterator descendingIterator() { - return mBuffer.descendingIterator(); - } - public Iterator ascendingIterator() { - return mBuffer.iterator(); - } - public Iterator filter( - final Iterator iter, final String pkg, final int userId) { - return new Iterator() { - StatusBarNotification mNext = findNext(); - - private StatusBarNotification findNext() { - while (iter.hasNext()) { - StatusBarNotification nr = iter.next(); - if ((pkg == null || nr.getPackageName() == pkg) - && (userId == UserHandle.USER_ALL || nr.getUserId() == userId)) { - return nr; - } - } - return null; - } - - @Override - public boolean hasNext() { - return mNext == null; - } - - @Override - public StatusBarNotification next() { - StatusBarNotification next = mNext; - if (next == null) { - throw new NoSuchElementException(); - } - mNext = findNext(); - return next; - } - - @Override - public void remove() { - iter.remove(); - } - }; - } - - public StatusBarNotification[] getArray(int count) { - if (count == 0) count = Archive.BUFFER_SIZE; - final StatusBarNotification[] a - = new StatusBarNotification[Math.min(count, mBuffer.size())]; - Iterator iter = descendingIterator(); - int i=0; - while (iter.hasNext() && i < count) { - a[i++] = iter.next(); - } - return a; - } - - public StatusBarNotification[] getArray(int count, String pkg, int userId) { - if (count == 0) count = Archive.BUFFER_SIZE; - final StatusBarNotification[] a - = new StatusBarNotification[Math.min(count, mBuffer.size())]; - Iterator iter = filter(descendingIterator(), pkg, userId); - int i=0; - while (iter.hasNext() && i < count) { - a[i++] = iter.next(); - } - return a; - } - - } - - Archive mArchive = new Archive(); - - private void loadBlockDb() { - synchronized(mBlockedPackages) { - if (mPolicyFile == null) { - File dir = new File("/data/system"); - mPolicyFile = new AtomicFile(new File(dir, "notification_policy.xml")); - - mBlockedPackages.clear(); - - FileInputStream infile = null; - try { - infile = mPolicyFile.openRead(); - final XmlPullParser parser = Xml.newPullParser(); - parser.setInput(infile, null); - - int type; - String tag; - int version = DB_VERSION; - while ((type = parser.next()) != END_DOCUMENT) { - tag = parser.getName(); - if (type == START_TAG) { - if (TAG_BODY.equals(tag)) { - version = Integer.parseInt(parser.getAttributeValue(null, ATTR_VERSION)); - } else if (TAG_BLOCKED_PKGS.equals(tag)) { - while ((type = parser.next()) != END_DOCUMENT) { - tag = parser.getName(); - if (TAG_PACKAGE.equals(tag)) { - mBlockedPackages.add(parser.getAttributeValue(null, ATTR_NAME)); - } else if (TAG_BLOCKED_PKGS.equals(tag) && type == END_TAG) { - break; - } - } - } - } - } - } catch (FileNotFoundException e) { - // No data yet - } catch (IOException e) { - Log.wtf(TAG, "Unable to read blocked notifications database", e); - } catch (NumberFormatException e) { - Log.wtf(TAG, "Unable to parse blocked notifications database", e); - } catch (XmlPullParserException e) { - Log.wtf(TAG, "Unable to parse blocked notifications database", e); - } finally { - IoUtils.closeQuietly(infile); - } - } - } - } - - /** - * Use this when you just want to know if notifications are OK for this package. - */ - public boolean areNotificationsEnabledForPackage(String pkg, int uid) { - checkCallerIsSystem(); - return (mAppOps.checkOpNoThrow(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg) - == AppOpsManager.MODE_ALLOWED); - } - - /** Use this when you actually want to post a notification or toast. - * - * Unchecked. Not exposed via Binder, but can be called in the course of enqueue*(). - */ - private boolean noteNotificationOp(String pkg, int uid) { - if (mAppOps.noteOpNoThrow(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg) - != AppOpsManager.MODE_ALLOWED) { - Slog.v(TAG, "notifications are disabled by AppOps for " + pkg); - return false; - } - return true; - } - - public void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled) { - checkCallerIsSystem(); - - Slog.v(TAG, (enabled?"en":"dis") + "abling notifications for " + pkg); - - mAppOps.setMode(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg, - enabled ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED); - - // Now, cancel any outstanding notifications that are part of a just-disabled app - if (ENABLE_BLOCKED_NOTIFICATIONS && !enabled) { - cancelAllNotificationsInt(pkg, 0, 0, true, UserHandle.getUserId(uid)); - } - } - - - private static String idDebugString(Context baseContext, String packageName, int id) { - Context c = null; - - if (packageName != null) { - try { - c = baseContext.createPackageContext(packageName, 0); - } catch (NameNotFoundException e) { - c = baseContext; - } - } else { - c = baseContext; - } - - String pkg; - String type; - String name; - - Resources r = c.getResources(); - try { - return r.getResourceName(id); - } catch (Resources.NotFoundException e) { - return ""; - } - } - - /** - * System-only API for getting a list of current (i.e. not cleared) notifications. - * - * Requires ACCESS_NOTIFICATIONS which is signature|system. - */ - @Override - public StatusBarNotification[] getActiveNotifications(String callingPkg) { - // enforce() will ensure the calling uid has the correct permission - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_NOTIFICATIONS, - "NotificationManagerService.getActiveNotifications"); - - StatusBarNotification[] tmp = null; - int uid = Binder.getCallingUid(); - - // noteOp will check to make sure the callingPkg matches the uid - if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg) - == AppOpsManager.MODE_ALLOWED) { - synchronized (mNotificationList) { - tmp = new StatusBarNotification[mNotificationList.size()]; - final int N = mNotificationList.size(); - for (int i=0; i installedServices = pm.queryIntentServicesAsUser( - new Intent(NotificationListenerService.SERVICE_INTERFACE), - PackageManager.GET_SERVICES | PackageManager.GET_META_DATA, - currentUser); - - Set installed = new HashSet(); - for (int i = 0, count = installedServices.size(); i < count; i++) { - ResolveInfo resolveInfo = installedServices.get(i); - ServiceInfo info = resolveInfo.serviceInfo; - - if (!android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE.equals( - info.permission)) { - Slog.w(TAG, "Skipping notification listener service " - + info.packageName + "/" + info.name - + ": it does not require the permission " - + android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE); - continue; - } - installed.add(new ComponentName(info.packageName, info.name)); - } - - String flatOut = ""; - if (!installed.isEmpty()) { - String[] enabled = flatIn.split(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR); - ArrayList remaining = new ArrayList(enabled.length); - for (int i = 0; i < enabled.length; i++) { - ComponentName enabledComponent = ComponentName.unflattenFromString(enabled[i]); - if (installed.contains(enabledComponent)) { - remaining.add(enabled[i]); - } - } - flatOut = TextUtils.join(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR, remaining); - } - if (DBG) Slog.v(TAG, "flat after: " + flatOut); - if (!flatIn.equals(flatOut)) { - Settings.Secure.putStringForUser(mContext.getContentResolver(), - Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, - flatOut, currentUser); - } - } - } - - /** - * Called whenever packages change, the user switches, or ENABLED_NOTIFICATION_LISTENERS - * is altered. (For example in response to USER_SWITCHED in our broadcast receiver) - */ - void rebindListenerServices() { - final int currentUser = ActivityManager.getCurrentUser(); - String flat = Settings.Secure.getStringForUser( - mContext.getContentResolver(), - Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, - currentUser); - - NotificationListenerInfo[] toRemove = new NotificationListenerInfo[mListeners.size()]; - final ArrayList toAdd; - - synchronized (mNotificationList) { - // unbind and remove all existing listeners - toRemove = mListeners.toArray(toRemove); - - toAdd = new ArrayList(); - final HashSet newEnabled = new HashSet(); - final HashSet newPackages = new HashSet(); - - // decode the list of components - if (flat != null) { - String[] components = flat.split(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR); - for (int i=0; i=0; i--) { - final NotificationListenerInfo info = mListeners.get(i); - if (name.equals(info.component) - && info.userid == userid) { - // cut old connections - if (DBG) Slog.v(TAG, " disconnecting old listener: " + info.listener); - mListeners.remove(i); - if (info.connection != null) { - mContext.unbindService(info.connection); - } - } - } - - Intent intent = new Intent(NotificationListenerService.SERVICE_INTERFACE); - intent.setComponent(name); - - intent.putExtra(Intent.EXTRA_CLIENT_LABEL, - R.string.notification_listener_binding_label); - intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity( - mContext, 0, new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS), 0)); - - try { - if (DBG) Slog.v(TAG, "binding: " + intent); - if (!mContext.bindServiceAsUser(intent, - new ServiceConnection() { - INotificationListener mListener; - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - synchronized (mNotificationList) { - mServicesBinding.remove(servicesBindingTag); - try { - mListener = INotificationListener.Stub.asInterface(service); - NotificationListenerInfo info = new NotificationListenerInfo( - mListener, name, userid, this); - service.linkToDeath(info, 0); - mListeners.add(info); - } catch (RemoteException e) { - // already dead - } - } - } - - @Override - public void onServiceDisconnected(ComponentName name) { - Slog.v(TAG, "notification listener connection lost: " + name); - } - }, - Context.BIND_AUTO_CREATE, - new UserHandle(userid))) - { - mServicesBinding.remove(servicesBindingTag); - Slog.w(TAG, "Unable to bind listener service: " + intent); - return; - } - } catch (SecurityException ex) { - Slog.e(TAG, "Unable to bind listener service: " + intent, ex); - return; - } - } - } - - /** - * Remove a listener binder directly - */ - @Override - public void unregisterListener(INotificationListener listener, int userid) { - // no need to check permissions; if your listener binder is in the list, - // that's proof that you had permission to add it in the first place - - synchronized (mNotificationList) { - final int N = mListeners.size(); - for (int i=N-1; i>=0; i--) { - final NotificationListenerInfo info = mListeners.get(i); - if (info.listener.asBinder() == listener.asBinder() - && info.userid == userid) { - mListeners.remove(i); - if (info.connection != null) { - mContext.unbindService(info.connection); - } - } - } - } - } - - /** - * Remove a listener service for the given user by ComponentName - */ - private void unregisterListenerService(ComponentName name, int userid) { - checkCallerIsSystem(); - - synchronized (mNotificationList) { - final int N = mListeners.size(); - for (int i=N-1; i>=0; i--) { - final NotificationListenerInfo info = mListeners.get(i); - if (name.equals(info.component) - && info.userid == userid) { - mListeners.remove(i); - if (info.connection != null) { - try { - mContext.unbindService(info.connection); - } catch (IllegalArgumentException ex) { - // something happened to the service: we think we have a connection - // but it's bogus. - Slog.e(TAG, "Listener " + name + " could not be unbound: " + ex); - } - } - } - } - } - } - - /** - * asynchronously notify all listeners about a new notification - */ - private void notifyPostedLocked(NotificationRecord n) { - // make a copy in case changes are made to the underlying Notification object - final StatusBarNotification sbn = n.sbn.clone(); - for (final NotificationListenerInfo info : mListeners) { - mHandler.post(new Runnable() { - @Override - public void run() { - info.notifyPostedIfUserMatch(sbn); - }}); - } - } - - /** - * asynchronously notify all listeners about a removed notification - */ - private void notifyRemovedLocked(NotificationRecord n) { - // make a copy in case changes are made to the underlying Notification object - // NOTE: this copy is lightweight: it doesn't include heavyweight parts of the notification - final StatusBarNotification sbn_light = n.sbn.cloneLight(); - - for (final NotificationListenerInfo info : mListeners) { - mHandler.post(new Runnable() { - @Override - public void run() { - info.notifyRemovedIfUserMatch(sbn_light); - }}); - } - } - - // -- APIs to support listeners clicking/clearing notifications -- - - private NotificationListenerInfo checkListenerToken(INotificationListener listener) { - final IBinder token = listener.asBinder(); - final int N = mListeners.size(); - for (int i=0; i list = new ArrayList(); - synchronized (mNotificationList) { - final int N = mNotificationList.size(); - for (int i=0; i 0) { - pw.println(prefix + " actions={"); - final int N = notification.actions.length; - for (int i=0; i %s", - prefix, - i, - action.title, - action.actionIntent.toString() - )); - } - pw.println(prefix + " }"); - } - if (notification.extras != null && notification.extras.size() > 0) { - pw.println(prefix + " extras={"); - for (String key : notification.extras.keySet()) { - pw.print(prefix + " " + key + "="); - Object val = notification.extras.get(key); - if (val == null) { - pw.println("null"); - } else { - pw.print(val.toString()); - if (val instanceof Bitmap) { - pw.print(String.format(" (%dx%d)", - ((Bitmap) val).getWidth(), - ((Bitmap) val).getHeight())); - } else if (val.getClass().isArray()) { - pw.println(" {"); - final int N = Array.getLength(val); - for (int i=0; i 0) pw.println(","); - pw.print(prefix + " " + Array.get(val, i)); - } - pw.print("\n" + prefix + " }"); - } - pw.println(); - } - } - pw.println(prefix + " }"); - } - } - - @Override - public final String toString() { - return String.format( - "NotificationRecord(0x%08x: pkg=%s user=%s id=%d tag=%s score=%d: %s)", - System.identityHashCode(this), - this.sbn.getPackageName(), this.sbn.getUser(), this.sbn.getId(), this.sbn.getTag(), - this.sbn.getScore(), this.sbn.getNotification()); - } - } - - private static final class ToastRecord - { - final int pid; - final String pkg; - final ITransientNotification callback; - int duration; - - ToastRecord(int pid, String pkg, ITransientNotification callback, int duration) - { - this.pid = pid; - this.pkg = pkg; - this.callback = callback; - this.duration = duration; - } - - void update(int duration) { - this.duration = duration; - } - - void dump(PrintWriter pw, String prefix) { - pw.println(prefix + this); - } - - @Override - public final String toString() - { - return "ToastRecord{" - + Integer.toHexString(System.identityHashCode(this)) - + " pkg=" + pkg - + " callback=" + callback - + " duration=" + duration; - } - } - - private StatusBarManagerService.NotificationCallbacks mNotificationCallbacks - = new StatusBarManagerService.NotificationCallbacks() { - - public void onSetDisabled(int status) { - synchronized (mNotificationList) { - mDisabledNotifications = status; - if ((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) { - // cancel whatever's going on - long identity = Binder.clearCallingIdentity(); - try { - final IRingtonePlayer player = mAudioService.getRingtonePlayer(); - if (player != null) { - player.stopAsync(); - } - } catch (RemoteException e) { - } finally { - Binder.restoreCallingIdentity(identity); - } - - identity = Binder.clearCallingIdentity(); - try { - mVibrator.cancel(); - } finally { - Binder.restoreCallingIdentity(identity); - } - } - } - } - - public void onClearAll() { - // XXX to be totally correct, the caller should tell us which user - // this is for. - cancelAll(ActivityManager.getCurrentUser()); - } - - public void onNotificationClick(String pkg, String tag, int id) { - // XXX to be totally correct, the caller should tell us which user - // this is for. - cancelNotification(pkg, tag, id, Notification.FLAG_AUTO_CANCEL, - Notification.FLAG_FOREGROUND_SERVICE, false, - ActivityManager.getCurrentUser()); - } - - public void onNotificationClear(String pkg, String tag, int id) { - // XXX to be totally correct, the caller should tell us which user - // this is for. - cancelNotification(pkg, tag, id, 0, - Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE, - true, ActivityManager.getCurrentUser()); - } - - public void onPanelRevealed() { - synchronized (mNotificationList) { - // sound - mSoundNotification = null; - - long identity = Binder.clearCallingIdentity(); - try { - final IRingtonePlayer player = mAudioService.getRingtonePlayer(); - if (player != null) { - player.stopAsync(); - } - } catch (RemoteException e) { - } finally { - Binder.restoreCallingIdentity(identity); - } - - // vibrate - mVibrateNotification = null; - identity = Binder.clearCallingIdentity(); - try { - mVibrator.cancel(); - } finally { - Binder.restoreCallingIdentity(identity); - } - - // light - mLights.clear(); - mLedNotification = null; - updateLightsLocked(); - } - } - - public void onNotificationError(String pkg, String tag, int id, - int uid, int initialPid, String message) { - Slog.d(TAG, "onNotification error pkg=" + pkg + " tag=" + tag + " id=" + id - + "; will crashApplication(uid=" + uid + ", pid=" + initialPid + ")"); - // XXX to be totally correct, the caller should tell us which user - // this is for. - cancelNotification(pkg, tag, id, 0, 0, false, UserHandle.getUserId(uid)); - long ident = Binder.clearCallingIdentity(); - try { - ActivityManagerNative.getDefault().crashApplication(uid, initialPid, pkg, - "Bad notification posted from package " + pkg - + ": " + message); - } catch (RemoteException e) { - } - Binder.restoreCallingIdentity(ident); - } - }; - - private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - - boolean queryRestart = false; - boolean queryRemove = false; - boolean packageChanged = false; - boolean cancelNotifications = true; - - if (action.equals(Intent.ACTION_PACKAGE_ADDED) - || (queryRemove=action.equals(Intent.ACTION_PACKAGE_REMOVED)) - || action.equals(Intent.ACTION_PACKAGE_RESTARTED) - || (packageChanged=action.equals(Intent.ACTION_PACKAGE_CHANGED)) - || (queryRestart=action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART)) - || action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) { - String pkgList[] = null; - boolean queryReplace = queryRemove && - intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); - if (DBG) Slog.i(TAG, "queryReplace=" + queryReplace); - if (action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) { - pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); - } else if (queryRestart) { - pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES); - } else { - Uri uri = intent.getData(); - if (uri == null) { - return; - } - String pkgName = uri.getSchemeSpecificPart(); - if (pkgName == null) { - return; - } - if (packageChanged) { - // We cancel notifications for packages which have just been disabled - try { - final int enabled = mContext.getPackageManager() - .getApplicationEnabledSetting(pkgName); - if (enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED - || enabled == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) { - cancelNotifications = false; - } - } catch (IllegalArgumentException e) { - // Package doesn't exist; probably racing with uninstall. - // cancelNotifications is already true, so nothing to do here. - if (DBG) { - Slog.i(TAG, "Exception trying to look up app enabled setting", e); - } - } - } - pkgList = new String[]{pkgName}; - } - - boolean anyListenersInvolved = false; - if (pkgList != null && (pkgList.length > 0)) { - for (String pkgName : pkgList) { - if (cancelNotifications) { - cancelAllNotificationsInt(pkgName, 0, 0, !queryRestart, - UserHandle.USER_ALL); - } - if (mEnabledListenerPackageNames.contains(pkgName)) { - anyListenersInvolved = true; - } - } - } - - if (anyListenersInvolved) { - // if we're not replacing a package, clean up orphaned bits - if (!queryReplace) { - disableNonexistentListeners(); - } - // make sure we're still bound to any of our - // listeners who may have just upgraded - rebindListenerServices(); - } - } else if (action.equals(Intent.ACTION_SCREEN_ON)) { - // Keep track of screen on/off state, but do not turn off the notification light - // until user passes through the lock screen or views the notification. - mScreenOn = true; - } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { - mScreenOn = false; - } else if (action.equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) { - mInCall = (intent.getStringExtra(TelephonyManager.EXTRA_STATE).equals( - TelephonyManager.EXTRA_STATE_OFFHOOK)); - updateNotificationPulse(); - } else if (action.equals(Intent.ACTION_USER_STOPPED)) { - int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); - if (userHandle >= 0) { - cancelAllNotificationsInt(null, 0, 0, true, userHandle); - } - } else if (action.equals(Intent.ACTION_USER_PRESENT)) { - // turn off LED when user passes through lock screen - mNotificationLight.turnOff(); - } else if (action.equals(Intent.ACTION_USER_SWITCHED)) { - // reload per-user settings - mSettingsObserver.update(null); - } - } - }; - - class SettingsObserver extends ContentObserver { - private final Uri NOTIFICATION_LIGHT_PULSE_URI - = Settings.System.getUriFor(Settings.System.NOTIFICATION_LIGHT_PULSE); - - private final Uri ENABLED_NOTIFICATION_LISTENERS_URI - = Settings.Secure.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS); - - SettingsObserver(Handler handler) { - super(handler); - } - - void observe() { - ContentResolver resolver = mContext.getContentResolver(); - resolver.registerContentObserver(NOTIFICATION_LIGHT_PULSE_URI, - false, this, UserHandle.USER_ALL); - resolver.registerContentObserver(ENABLED_NOTIFICATION_LISTENERS_URI, - false, this, UserHandle.USER_ALL); - update(null); - } - - @Override public void onChange(boolean selfChange, Uri uri) { - update(uri); - } - - public void update(Uri uri) { - ContentResolver resolver = mContext.getContentResolver(); - if (uri == null || NOTIFICATION_LIGHT_PULSE_URI.equals(uri)) { - boolean pulseEnabled = Settings.System.getInt(resolver, - Settings.System.NOTIFICATION_LIGHT_PULSE, 0) != 0; - if (mNotificationPulseEnabled != pulseEnabled) { - mNotificationPulseEnabled = pulseEnabled; - updateNotificationPulse(); - } - } - if (uri == null || ENABLED_NOTIFICATION_LISTENERS_URI.equals(uri)) { - rebindListenerServices(); - } - } - } - - private SettingsObserver mSettingsObserver; - - static long[] getLongArray(Resources r, int resid, int maxlen, long[] def) { - int[] ar = r.getIntArray(resid); - if (ar == null) { - return def; - } - final int len = ar.length > maxlen ? maxlen : ar.length; - long[] out = new long[len]; - for (int i=0; i(); - mHandler = new WorkerHandler(); - - mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE); - - importOldBlockDb(); - - mStatusBar = statusBar; - statusBar.setNotificationCallbacks(mNotificationCallbacks); - - mNotificationLight = lights.getLight(LightsService.LIGHT_ID_NOTIFICATIONS); - mAttentionLight = lights.getLight(LightsService.LIGHT_ID_ATTENTION); - - Resources resources = mContext.getResources(); - mDefaultNotificationColor = resources.getColor( - R.color.config_defaultNotificationColor); - mDefaultNotificationLedOn = resources.getInteger( - R.integer.config_defaultNotificationLedOn); - mDefaultNotificationLedOff = resources.getInteger( - R.integer.config_defaultNotificationLedOff); - - mDefaultVibrationPattern = getLongArray(resources, - R.array.config_defaultNotificationVibePattern, - VIBRATE_PATTERN_MAXLEN, - DEFAULT_VIBRATE_PATTERN); - - mFallbackVibrationPattern = getLongArray(resources, - R.array.config_notificationFallbackVibePattern, - VIBRATE_PATTERN_MAXLEN, - DEFAULT_VIBRATE_PATTERN); - - // 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 - // flag at least once and we'll go back to 0 after that. - if (0 == Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.DEVICE_PROVISIONED, 0)) { - mDisabledNotifications = StatusBarManager.DISABLE_NOTIFICATION_ALERTS; - } - - // register for various Intents - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_SCREEN_ON); - filter.addAction(Intent.ACTION_SCREEN_OFF); - filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); - filter.addAction(Intent.ACTION_USER_PRESENT); - filter.addAction(Intent.ACTION_USER_STOPPED); - filter.addAction(Intent.ACTION_USER_SWITCHED); - mContext.registerReceiver(mIntentReceiver, filter); - IntentFilter pkgFilter = new IntentFilter(); - pkgFilter.addAction(Intent.ACTION_PACKAGE_ADDED); - pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); - pkgFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); - pkgFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED); - pkgFilter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART); - pkgFilter.addDataScheme("package"); - mContext.registerReceiver(mIntentReceiver, pkgFilter); - IntentFilter sdFilter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); - mContext.registerReceiver(mIntentReceiver, sdFilter); - - mSettingsObserver = new SettingsObserver(mHandler); - mSettingsObserver.observe(); - - // spin up NotificationScorers - String[] notificationScorerNames = resources.getStringArray( - R.array.config_notificationScorers); - for (String scorerName : notificationScorerNames) { - try { - Class scorerClass = mContext.getClassLoader().loadClass(scorerName); - NotificationScorer scorer = (NotificationScorer) scorerClass.newInstance(); - scorer.initialize(mContext); - mScorers.add(scorer); - } catch (ClassNotFoundException e) { - Slog.w(TAG, "Couldn't find scorer " + scorerName + ".", e); - } catch (InstantiationException e) { - Slog.w(TAG, "Couldn't instantiate scorer " + scorerName + ".", e); - } catch (IllegalAccessException e) { - Slog.w(TAG, "Problem accessing scorer " + scorerName + ".", e); - } - } - } - - /** - * Read the old XML-based app block database and import those blockages into the AppOps system. - */ - private void importOldBlockDb() { - loadBlockDb(); - - PackageManager pm = mContext.getPackageManager(); - for (String pkg : mBlockedPackages) { - PackageInfo info = null; - try { - info = pm.getPackageInfo(pkg, 0); - setNotificationsEnabledForPackage(pkg, info.applicationInfo.uid, false); - } catch (NameNotFoundException e) { - // forget you - } - } - mBlockedPackages.clear(); - if (mPolicyFile != null) { - mPolicyFile.delete(); - } - } - - void systemReady() { - mAudioService = IAudioService.Stub.asInterface( - ServiceManager.getService(Context.AUDIO_SERVICE)); - - // no beeping until we're basically done booting - mSystemReady = true; - - // make sure our listener services are properly bound - rebindListenerServices(); - } - - // Toasts - // ============================================================================ - public void enqueueToast(String pkg, ITransientNotification callback, int duration) - { - if (DBG) Slog.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback + " duration=" + duration); - - if (pkg == null || callback == null) { - Slog.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback); - return ; - } - - final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg)); - - if (ENABLE_BLOCKED_TOASTS && !noteNotificationOp(pkg, Binder.getCallingUid())) { - if (!isSystemToast) { - Slog.e(TAG, "Suppressing toast from package " + pkg + " by user request."); - return; - } - } - - synchronized (mToastQueue) { - int callingPid = Binder.getCallingPid(); - long callingId = Binder.clearCallingIdentity(); - try { - ToastRecord record; - int index = indexOfToastLocked(pkg, callback); - // If it's already in the queue, we update it in place, we don't - // move it to the end of the queue. - if (index >= 0) { - record = mToastQueue.get(index); - record.update(duration); - } else { - // Limit the number of toasts that any given package except the android - // package can enqueue. Prevents DOS attacks and deals with leaks. - if (!isSystemToast) { - int count = 0; - final int N = mToastQueue.size(); - for (int i=0; i= MAX_PACKAGE_NOTIFICATIONS) { - Slog.e(TAG, "Package has already posted " + count - + " toasts. Not showing more. Package=" + pkg); - return; - } - } - } - } - - record = new ToastRecord(callingPid, pkg, callback, duration); - mToastQueue.add(record); - index = mToastQueue.size() - 1; - keepProcessAliveLocked(callingPid); - } - // If it's at index 0, it's the current toast. It doesn't matter if it's - // new or just been updated. Call back and tell it to show itself. - // If the callback fails, this will remove it from the list, so don't - // assume that it's valid after this. - if (index == 0) { - showNextToastLocked(); - } - } finally { - Binder.restoreCallingIdentity(callingId); - } - } - } - - public void cancelToast(String pkg, ITransientNotification callback) { - Slog.i(TAG, "cancelToast pkg=" + pkg + " callback=" + callback); - - if (pkg == null || callback == null) { - Slog.e(TAG, "Not cancelling notification. pkg=" + pkg + " callback=" + callback); - return ; - } - - synchronized (mToastQueue) { - long callingId = Binder.clearCallingIdentity(); - try { - int index = indexOfToastLocked(pkg, callback); - if (index >= 0) { - cancelToastLocked(index); - } else { - Slog.w(TAG, "Toast already cancelled. pkg=" + pkg + " callback=" + callback); - } - } finally { - Binder.restoreCallingIdentity(callingId); - } - } - } - - private void showNextToastLocked() { - ToastRecord record = mToastQueue.get(0); - while (record != null) { - if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback); - try { - record.callback.show(); - scheduleTimeoutLocked(record); - return; - } catch (RemoteException e) { - Slog.w(TAG, "Object died trying to show notification " + record.callback - + " in package " + record.pkg); - // remove it from the list and let the process die - int index = mToastQueue.indexOf(record); - if (index >= 0) { - mToastQueue.remove(index); - } - keepProcessAliveLocked(record.pid); - if (mToastQueue.size() > 0) { - record = mToastQueue.get(0); - } else { - record = null; - } - } - } - } - - private void cancelToastLocked(int index) { - ToastRecord record = mToastQueue.get(index); - try { - record.callback.hide(); - } catch (RemoteException e) { - Slog.w(TAG, "Object died trying to hide notification " + record.callback - + " in package " + record.pkg); - // don't worry about this, we're about to remove it from - // the list anyway - } - mToastQueue.remove(index); - keepProcessAliveLocked(record.pid); - if (mToastQueue.size() > 0) { - // Show the next one. If the callback fails, this will remove - // it from the list, so don't assume that the list hasn't changed - // after this point. - showNextToastLocked(); - } - } - - private void scheduleTimeoutLocked(ToastRecord r) - { - mHandler.removeCallbacksAndMessages(r); - Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r); - long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY; - mHandler.sendMessageDelayed(m, delay); - } - - private void handleTimeout(ToastRecord record) - { - if (DBG) Slog.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback); - synchronized (mToastQueue) { - int index = indexOfToastLocked(record.pkg, record.callback); - if (index >= 0) { - cancelToastLocked(index); - } - } - } - - // lock on mToastQueue - private int indexOfToastLocked(String pkg, ITransientNotification callback) - { - IBinder cbak = callback.asBinder(); - ArrayList list = mToastQueue; - int len = list.size(); - for (int i=0; i list = mToastQueue; - int N = list.size(); - for (int i=0; i 0); - } catch (RemoteException e) { - // Shouldn't happen. - } - } - - private final class WorkerHandler extends Handler - { - @Override - public void handleMessage(Message msg) - { - switch (msg.what) - { - case MESSAGE_TIMEOUT: - handleTimeout((ToastRecord)msg.obj); - break; - } - } - } - - - // Notifications - // ============================================================================ - public void enqueueNotificationWithTag(String pkg, String basePkg, String tag, int id, - Notification notification, int[] idOut, int userId) - { - enqueueNotificationInternal(pkg, basePkg, Binder.getCallingUid(), Binder.getCallingPid(), - tag, id, notification, idOut, userId); - } - - private final static int clamp(int x, int low, int high) { - return (x < low) ? low : ((x > high) ? high : x); - } - - // Not exposed via Binder; for system use only (otherwise malicious apps could spoof the - // uid/pid of another application) - - public void enqueueNotificationInternal(final String pkg, String basePkg, final int callingUid, - final int callingPid, final String tag, final int id, final Notification notification, - int[] idOut, int incomingUserId) - { - if (DBG) { - Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id + " notification=" + notification); - } - checkCallerIsSystemOrSameApp(pkg); - final boolean isSystemNotification = isUidSystem(callingUid) || ("android".equals(pkg)); - - final int userId = ActivityManager.handleIncomingUser(callingPid, - callingUid, incomingUserId, true, false, "enqueueNotification", pkg); - final UserHandle user = new UserHandle(userId); - - // Limit the number of notifications that any given package except the android - // package can enqueue. Prevents DOS attacks and deals with leaks. - if (!isSystemNotification) { - synchronized (mNotificationList) { - int count = 0; - final int N = mNotificationList.size(); - for (int i=0; i= MAX_PACKAGE_NOTIFICATIONS) { - Slog.e(TAG, "Package has already posted " + count - + " notifications. Not showing more. package=" + pkg); - return; - } - } - } - } - } - - // This conditional is a dirty hack to limit the logging done on - // behalf of the download manager without affecting other apps. - if (!pkg.equals("com.android.providers.downloads") - || Log.isLoggable("DownloadManager", Log.VERBOSE)) { - EventLog.writeEvent(EventLogTags.NOTIFICATION_ENQUEUE, pkg, id, tag, userId, - notification.toString()); - } - - if (pkg == null || notification == null) { - throw new IllegalArgumentException("null not allowed: pkg=" + pkg - + " id=" + id + " notification=" + notification); - } - if (notification.icon != 0) { - if (notification.contentView == null) { - throw new IllegalArgumentException("contentView required: pkg=" + pkg - + " id=" + id + " notification=" + notification); - } - } - - mHandler.post(new Runnable() { - @Override - public void run() { - - // === Scoring === - - // 0. Sanitize inputs - notification.priority = clamp(notification.priority, Notification.PRIORITY_MIN, - Notification.PRIORITY_MAX); - // Migrate notification flags to scores - if (0 != (notification.flags & Notification.FLAG_HIGH_PRIORITY)) { - if (notification.priority < Notification.PRIORITY_MAX) { - notification.priority = Notification.PRIORITY_MAX; - } - } else if (SCORE_ONGOING_HIGHER && - 0 != (notification.flags & Notification.FLAG_ONGOING_EVENT)) { - if (notification.priority < Notification.PRIORITY_HIGH) { - notification.priority = Notification.PRIORITY_HIGH; - } - } - - // 1. initial score: buckets of 10, around the app - int score = notification.priority * NOTIFICATION_PRIORITY_MULTIPLIER; //[-20..20] - - // 2. Consult external heuristics (TBD) - - // 3. Apply local rules - - int initialScore = score; - if (!mScorers.isEmpty()) { - if (DBG) Slog.v(TAG, "Initial score is " + score + "."); - for (NotificationScorer scorer : mScorers) { - try { - score = scorer.getScore(notification, score); - } catch (Throwable t) { - Slog.w(TAG, "Scorer threw on .getScore.", t); - } - } - if (DBG) Slog.v(TAG, "Final score is " + score + "."); - } - - // add extra to indicate score modified by NotificationScorer - notification.extras.putBoolean(Notification.EXTRA_SCORE_MODIFIED, - score != initialScore); - - // blocked apps - if (ENABLE_BLOCKED_NOTIFICATIONS && !noteNotificationOp(pkg, callingUid)) { - if (!isSystemNotification) { - score = JUNK_SCORE; - Slog.e(TAG, "Suppressing notification from package " + pkg - + " by user request."); - } - } - - if (DBG) { - Slog.v(TAG, "Assigned score=" + score + " to " + notification); - } - - if (score < SCORE_DISPLAY_THRESHOLD) { - // Notification will be blocked because the score is too low. - return; - } - - // Should this notification make noise, vibe, or use the LED? - final boolean canInterrupt = (score >= SCORE_INTERRUPTION_THRESHOLD); - - synchronized (mNotificationList) { - final StatusBarNotification n = new StatusBarNotification( - pkg, id, tag, callingUid, callingPid, score, notification, user); - NotificationRecord r = new NotificationRecord(n); - NotificationRecord old = null; - - int index = indexOfNotificationLocked(pkg, tag, id, userId); - if (index < 0) { - mNotificationList.add(r); - } else { - old = mNotificationList.remove(index); - mNotificationList.add(index, r); - // Make sure we don't lose the foreground service state. - if (old != null) { - notification.flags |= - old.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE; - } - } - - // Ensure if this is a foreground service that the proper additional - // flags are set. - if ((notification.flags&Notification.FLAG_FOREGROUND_SERVICE) != 0) { - notification.flags |= Notification.FLAG_ONGOING_EVENT - | Notification.FLAG_NO_CLEAR; - } - - final int currentUser; - final long token = Binder.clearCallingIdentity(); - try { - currentUser = ActivityManager.getCurrentUser(); - } finally { - Binder.restoreCallingIdentity(token); - } - - if (notification.icon != 0) { - if (old != null && old.statusBarKey != null) { - r.statusBarKey = old.statusBarKey; - long identity = Binder.clearCallingIdentity(); - try { - mStatusBar.updateNotification(r.statusBarKey, n); - } - finally { - Binder.restoreCallingIdentity(identity); - } - } else { - long identity = Binder.clearCallingIdentity(); - try { - r.statusBarKey = mStatusBar.addNotification(n); - if ((n.getNotification().flags & Notification.FLAG_SHOW_LIGHTS) != 0 - && canInterrupt) { - mAttentionLight.pulse(); - } - } - finally { - Binder.restoreCallingIdentity(identity); - } - } - // Send accessibility events only for the current user. - if (currentUser == userId) { - sendAccessibilityEvent(notification, pkg); - } - - notifyPostedLocked(r); - } else { - Slog.e(TAG, "Not posting notification with icon==0: " + notification); - if (old != null && old.statusBarKey != null) { - long identity = Binder.clearCallingIdentity(); - try { - mStatusBar.removeNotification(old.statusBarKey); - } - finally { - Binder.restoreCallingIdentity(identity); - } - - notifyRemovedLocked(r); - } - // ATTENTION: in a future release we will bail out here - // so that we do not play sounds, show lights, etc. for invalid notifications - Slog.e(TAG, "WARNING: In a future release this will crash the app: " - + n.getPackageName()); - } - - // If we're not supposed to beep, vibrate, etc. then don't. - if (((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) == 0) - && (!(old != null - && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 )) - && (r.getUserId() == UserHandle.USER_ALL || - (r.getUserId() == userId && r.getUserId() == currentUser)) - && canInterrupt - && mSystemReady) { - - final AudioManager audioManager = (AudioManager) mContext - .getSystemService(Context.AUDIO_SERVICE); - - // sound - - // should we use the default notification sound? (indicated either by - // DEFAULT_SOUND or because notification.sound is pointing at - // Settings.System.NOTIFICATION_SOUND) - final boolean useDefaultSound = - (notification.defaults & Notification.DEFAULT_SOUND) != 0 || - Settings.System.DEFAULT_NOTIFICATION_URI - .equals(notification.sound); - - Uri soundUri = null; - boolean hasValidSound = false; - - if (useDefaultSound) { - soundUri = Settings.System.DEFAULT_NOTIFICATION_URI; - - // check to see if the default notification sound is silent - ContentResolver resolver = mContext.getContentResolver(); - hasValidSound = Settings.System.getString(resolver, - Settings.System.NOTIFICATION_SOUND) != null; - } else if (notification.sound != null) { - soundUri = notification.sound; - hasValidSound = (soundUri != null); - } - - if (hasValidSound) { - boolean looping = (notification.flags & Notification.FLAG_INSISTENT) != 0; - int audioStreamType; - if (notification.audioStreamType >= 0) { - audioStreamType = notification.audioStreamType; - } else { - audioStreamType = DEFAULT_STREAM_TYPE; - } - mSoundNotification = r; - // do not play notifications if stream volume is 0 (typically because - // ringer mode is silent) or if there is a user of exclusive audio focus - if ((audioManager.getStreamVolume(audioStreamType) != 0) - && !audioManager.isAudioFocusExclusive()) { - final long identity = Binder.clearCallingIdentity(); - try { - final IRingtonePlayer player = mAudioService.getRingtonePlayer(); - if (player != null) { - player.playAsync(soundUri, user, looping, audioStreamType); - } - } catch (RemoteException e) { - } finally { - Binder.restoreCallingIdentity(identity); - } - } - } - - // vibrate - // Does the notification want to specify its own vibration? - final boolean hasCustomVibrate = notification.vibrate != null; - - // new in 4.2: if there was supposed to be a sound and we're in vibrate - // mode, and no other vibration is specified, we fall back to vibration - final boolean convertSoundToVibration = - !hasCustomVibrate - && hasValidSound - && (audioManager.getRingerMode() - == AudioManager.RINGER_MODE_VIBRATE); - - // The DEFAULT_VIBRATE flag trumps any custom vibration AND the fallback. - final boolean useDefaultVibrate = - (notification.defaults & Notification.DEFAULT_VIBRATE) != 0; - - if ((useDefaultVibrate || convertSoundToVibration || hasCustomVibrate) - && !(audioManager.getRingerMode() - == AudioManager.RINGER_MODE_SILENT)) { - mVibrateNotification = r; - - if (useDefaultVibrate || convertSoundToVibration) { - // Escalate privileges so we can use the vibrator even if the - // notifying app does not have the VIBRATE permission. - long identity = Binder.clearCallingIdentity(); - try { - mVibrator.vibrate(r.sbn.getUid(), r.sbn.getBasePkg(), - useDefaultVibrate ? mDefaultVibrationPattern - : mFallbackVibrationPattern, - ((notification.flags & Notification.FLAG_INSISTENT) != 0) - ? 0: -1); - } finally { - Binder.restoreCallingIdentity(identity); - } - } else if (notification.vibrate.length > 1) { - // If you want your own vibration pattern, you need the VIBRATE - // permission - mVibrator.vibrate(r.sbn.getUid(), r.sbn.getBasePkg(), - notification.vibrate, - ((notification.flags & Notification.FLAG_INSISTENT) != 0) - ? 0: -1); - } - } - } - - // light - // the most recent thing gets the light - mLights.remove(old); - if (mLedNotification == old) { - mLedNotification = null; - } - //Slog.i(TAG, "notification.lights=" - // + ((old.notification.lights.flags & Notification.FLAG_SHOW_LIGHTS) - // != 0)); - if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0 - && canInterrupt) { - mLights.add(r); - updateLightsLocked(); - } else { - if (old != null - && ((old.getFlags() & Notification.FLAG_SHOW_LIGHTS) != 0)) { - updateLightsLocked(); - } - } - } - } - }); - - idOut[0] = id; - } - - private void sendAccessibilityEvent(Notification notification, CharSequence packageName) { - AccessibilityManager manager = AccessibilityManager.getInstance(mContext); - if (!manager.isEnabled()) { - return; - } - - AccessibilityEvent event = - AccessibilityEvent.obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED); - event.setPackageName(packageName); - event.setClassName(Notification.class.getName()); - event.setParcelableData(notification); - CharSequence tickerText = notification.tickerText; - if (!TextUtils.isEmpty(tickerText)) { - event.getText().add(tickerText); - } - - manager.sendAccessibilityEvent(event); - } - - private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete) { - // tell the app - if (sendDelete) { - if (r.getNotification().deleteIntent != null) { - try { - r.getNotification().deleteIntent.send(); - } catch (PendingIntent.CanceledException ex) { - // do nothing - there's no relevant way to recover, and - // no reason to let this propagate - Slog.w(TAG, "canceled PendingIntent for " + r.sbn.getPackageName(), ex); - } - } - } - - // status bar - if (r.getNotification().icon != 0) { - long identity = Binder.clearCallingIdentity(); - try { - mStatusBar.removeNotification(r.statusBarKey); - } - finally { - Binder.restoreCallingIdentity(identity); - } - r.statusBarKey = null; - notifyRemovedLocked(r); - } - - // sound - if (mSoundNotification == r) { - mSoundNotification = null; - final long identity = Binder.clearCallingIdentity(); - try { - final IRingtonePlayer player = mAudioService.getRingtonePlayer(); - if (player != null) { - player.stopAsync(); - } - } catch (RemoteException e) { - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - // vibrate - if (mVibrateNotification == r) { - mVibrateNotification = null; - long identity = Binder.clearCallingIdentity(); - try { - mVibrator.cancel(); - } - finally { - Binder.restoreCallingIdentity(identity); - } - } - - // light - mLights.remove(r); - if (mLedNotification == r) { - mLedNotification = null; - } - - // Save it for users of getHistoricalNotifications() - mArchive.record(r.sbn); - } - - /** - * Cancels a notification ONLY if it has all of the {@code mustHaveFlags} - * and none of the {@code mustNotHaveFlags}. - */ - private void cancelNotification(final String pkg, final String tag, final int id, - final int mustHaveFlags, final int mustNotHaveFlags, final boolean sendDelete, - final int userId) { - // In enqueueNotificationInternal notifications are added by scheduling the - // work on the worker handler. Hence, we also schedule the cancel on this - // handler to avoid a scenario where an add notification call followed by a - // remove notification call ends up in not removing the notification. - mHandler.post(new Runnable() { - @Override - public void run() { - EventLog.writeEvent(EventLogTags.NOTIFICATION_CANCEL, pkg, id, tag, userId, - mustHaveFlags, mustNotHaveFlags); - - synchronized (mNotificationList) { - int index = indexOfNotificationLocked(pkg, tag, id, userId); - if (index >= 0) { - NotificationRecord r = mNotificationList.get(index); - - if ((r.getNotification().flags & mustHaveFlags) != mustHaveFlags) { - return; - } - if ((r.getNotification().flags & mustNotHaveFlags) != 0) { - return; - } - - mNotificationList.remove(index); - - cancelNotificationLocked(r, sendDelete); - updateLightsLocked(); - } - } - } - }); - } - - /** - * Determine whether the userId applies to the notification in question, either because - * they match exactly, or one of them is USER_ALL (which is treated as a wildcard). - */ - private boolean notificationMatchesUserId(NotificationRecord r, int userId) { - return - // looking for USER_ALL notifications? match everything - userId == UserHandle.USER_ALL - // a notification sent to USER_ALL matches any query - || r.getUserId() == UserHandle.USER_ALL - // an exact user match - || r.getUserId() == userId; - } - - /** - * Cancels all notifications from a given package that have all of the - * {@code mustHaveFlags}. - */ - boolean cancelAllNotificationsInt(String pkg, int mustHaveFlags, - int mustNotHaveFlags, boolean doit, int userId) { - EventLog.writeEvent(EventLogTags.NOTIFICATION_CANCEL_ALL, pkg, userId, - mustHaveFlags, mustNotHaveFlags); - - synchronized (mNotificationList) { - final int N = mNotificationList.size(); - boolean canceledSomething = false; - for (int i = N-1; i >= 0; --i) { - NotificationRecord r = mNotificationList.get(i); - if (!notificationMatchesUserId(r, userId)) { - continue; - } - // Don't remove notifications to all, if there's no package name specified - if (r.getUserId() == UserHandle.USER_ALL && pkg == null) { - continue; - } - if ((r.getFlags() & mustHaveFlags) != mustHaveFlags) { - continue; - } - if ((r.getFlags() & mustNotHaveFlags) != 0) { - continue; - } - if (pkg != null && !r.sbn.getPackageName().equals(pkg)) { - continue; - } - canceledSomething = true; - if (!doit) { - return true; - } - mNotificationList.remove(i); - cancelNotificationLocked(r, false); - } - if (canceledSomething) { - updateLightsLocked(); - } - return canceledSomething; - } - } - - public void cancelNotificationWithTag(String pkg, String tag, int id, int userId) { - checkCallerIsSystemOrSameApp(pkg); - userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), - Binder.getCallingUid(), userId, true, false, "cancelNotificationWithTag", pkg); - // Don't allow client applications to cancel foreground service notis. - cancelNotification(pkg, tag, id, 0, - Binder.getCallingUid() == Process.SYSTEM_UID - ? 0 : Notification.FLAG_FOREGROUND_SERVICE, false, userId); - } - - public void cancelAllNotifications(String pkg, int userId) { - checkCallerIsSystemOrSameApp(pkg); - - userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), - Binder.getCallingUid(), userId, true, false, "cancelAllNotifications", pkg); - - // Calling from user space, don't allow the canceling of actively - // running foreground services. - cancelAllNotificationsInt(pkg, 0, Notification.FLAG_FOREGROUND_SERVICE, true, userId); - } - - // Return true if the UID is a system or phone UID and therefore should not have - // any notifications or toasts blocked. - boolean isUidSystem(int uid) { - final int appid = UserHandle.getAppId(uid); - return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0); - } - - // same as isUidSystem(int, int) for the Binder caller's UID. - boolean isCallerSystem() { - return isUidSystem(Binder.getCallingUid()); - } - - void checkCallerIsSystem() { - if (isCallerSystem()) { - return; - } - throw new SecurityException("Disallowed call for uid " + Binder.getCallingUid()); - } - - void checkCallerIsSystemOrSameApp(String pkg) { - if (isCallerSystem()) { - return; - } - final int uid = Binder.getCallingUid(); - try { - ApplicationInfo ai = AppGlobals.getPackageManager().getApplicationInfo( - pkg, 0, UserHandle.getCallingUserId()); - if (!UserHandle.isSameApp(ai.uid, uid)) { - throw new SecurityException("Calling uid " + uid + " gave package" - + pkg + " which is owned by uid " + ai.uid); - } - } catch (RemoteException re) { - throw new SecurityException("Unknown package " + pkg + "\n" + re); - } - } - - void cancelAll(int userId) { - synchronized (mNotificationList) { - final int N = mNotificationList.size(); - for (int i=N-1; i>=0; i--) { - NotificationRecord r = mNotificationList.get(i); - - if (!notificationMatchesUserId(r, userId)) { - continue; - } - - if ((r.getFlags() & (Notification.FLAG_ONGOING_EVENT - | Notification.FLAG_NO_CLEAR)) == 0) { - mNotificationList.remove(i); - cancelNotificationLocked(r, true); - } - } - - updateLightsLocked(); - } - } - - // lock on mNotificationList - private void updateLightsLocked() - { - // handle notification lights - if (mLedNotification == null) { - // get next notification, if any - int n = mLights.size(); - if (n > 0) { - mLedNotification = mLights.get(n-1); - } - } - - // Don't flash while we are in a call or screen is on - if (mLedNotification == null || mInCall || mScreenOn) { - mNotificationLight.turnOff(); - } else { - final Notification ledno = mLedNotification.sbn.getNotification(); - int ledARGB = ledno.ledARGB; - int ledOnMS = ledno.ledOnMS; - int ledOffMS = ledno.ledOffMS; - if ((ledno.defaults & Notification.DEFAULT_LIGHTS) != 0) { - ledARGB = mDefaultNotificationColor; - ledOnMS = mDefaultNotificationLedOn; - ledOffMS = mDefaultNotificationLedOff; - } - if (mNotificationPulseEnabled) { - // pulse repeatedly - mNotificationLight.setFlashing(ledARGB, LightsService.LIGHT_FLASH_TIMED, - ledOnMS, ledOffMS); - } - } - } - - // lock on mNotificationList - private int indexOfNotificationLocked(String pkg, String tag, int id, int userId) - { - ArrayList list = mNotificationList; - final int len = list.size(); - for (int i=0; i 0) { - pw.println(" Toast Queue:"); - for (int i=0; i 0) { - pw.println(" Notification List:"); - for (int i=0; i 0) { - pw.println(" Lights List:"); - for (int i=0; i= 5) { - if (iter.hasNext()) pw.println(" ..."); - break; - } - } - - } - } -} diff --git a/services/java/com/android/server/StatusBarManagerService.java b/services/java/com/android/server/StatusBarManagerService.java deleted file mode 100644 index f207c08c579c..000000000000 --- a/services/java/com/android/server/StatusBarManagerService.java +++ /dev/null @@ -1,661 +0,0 @@ -/* - * Copyright (C) 2007 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 android.app.StatusBarManager; -import android.service.notification.StatusBarNotification; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.os.Binder; -import android.os.Handler; -import android.os.IBinder; -import android.os.RemoteException; -import android.os.UserHandle; -import android.util.Slog; - -import com.android.internal.statusbar.IStatusBar; -import com.android.internal.statusbar.IStatusBarService; -import com.android.internal.statusbar.StatusBarIcon; -import com.android.internal.statusbar.StatusBarIconList; -import com.android.server.wm.WindowManagerService; - -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - - -/** - * A note on locking: We rely on the fact that calls onto mBar are oneway or - * if they are local, that they just enqueue messages to not deadlock. - */ -public class StatusBarManagerService extends IStatusBarService.Stub - implements WindowManagerService.OnHardKeyboardStatusChangeListener -{ - static final String TAG = "StatusBarManagerService"; - static final boolean SPEW = false; - - final Context mContext; - final WindowManagerService mWindowManager; - Handler mHandler = new Handler(); - NotificationCallbacks mNotificationCallbacks; - volatile IStatusBar mBar; - StatusBarIconList mIcons = new StatusBarIconList(); - HashMap mNotifications - = new HashMap(); - - // for disabling the status bar - final ArrayList mDisableRecords = new ArrayList(); - IBinder mSysUiVisToken = new Binder(); - int mDisabled = 0; - - Object mLock = new Object(); - // encompasses lights-out mode and other flags defined on View - int mSystemUiVisibility = 0; - boolean mMenuVisible = false; - int mImeWindowVis = 0; - int mImeBackDisposition; - IBinder mImeToken = null; - int mCurrentUserId; - - private class DisableRecord implements IBinder.DeathRecipient { - int userId; - String pkg; - int what; - IBinder token; - - public void binderDied() { - Slog.i(TAG, "binder died for pkg=" + pkg); - disableInternal(userId, 0, token, pkg); - token.unlinkToDeath(this, 0); - } - } - - public interface NotificationCallbacks { - void onSetDisabled(int status); - void onClearAll(); - void onNotificationClick(String pkg, String tag, int id); - void onNotificationClear(String pkg, String tag, int id); - void onPanelRevealed(); - void onNotificationError(String pkg, String tag, int id, - int uid, int initialPid, String message); - } - - /** - * Construct the service, add the status bar view to the window manager - */ - public StatusBarManagerService(Context context, WindowManagerService windowManager) { - mContext = context; - mWindowManager = windowManager; - mWindowManager.setOnHardKeyboardStatusChangeListener(this); - - final Resources res = context.getResources(); - mIcons.defineSlots(res.getStringArray(com.android.internal.R.array.config_statusBarIcons)); - } - - public void setNotificationCallbacks(NotificationCallbacks listener) { - mNotificationCallbacks = listener; - } - - // ================================================================================ - // From IStatusBarService - // ================================================================================ - public void expandNotificationsPanel() { - enforceExpandStatusBar(); - - if (mBar != null) { - try { - mBar.animateExpandNotificationsPanel(); - } catch (RemoteException ex) { - } - } - } - - public void collapsePanels() { - enforceExpandStatusBar(); - - if (mBar != null) { - try { - mBar.animateCollapsePanels(); - } catch (RemoteException ex) { - } - } - } - - public void expandSettingsPanel() { - enforceExpandStatusBar(); - - if (mBar != null) { - try { - mBar.animateExpandSettingsPanel(); - } catch (RemoteException ex) { - } - } - } - - public void disable(int what, IBinder token, String pkg) { - disableInternal(mCurrentUserId, what, token, pkg); - } - - private void disableInternal(int userId, int what, IBinder token, String pkg) { - enforceStatusBar(); - - synchronized (mLock) { - disableLocked(userId, what, token, pkg); - } - } - - private void disableLocked(int userId, int what, IBinder token, String pkg) { - // It's important that the the callback and the call to mBar get done - // in the same order when multiple threads are calling this function - // so they are paired correctly. The messages on the handler will be - // handled in the order they were enqueued, but will be outside the lock. - manageDisableListLocked(userId, what, token, pkg); - - // Ensure state for the current user is applied, even if passed a non-current user. - final int net = gatherDisableActionsLocked(mCurrentUserId); - if (net != mDisabled) { - mDisabled = net; - mHandler.post(new Runnable() { - public void run() { - mNotificationCallbacks.onSetDisabled(net); - } - }); - if (mBar != null) { - try { - mBar.disable(net); - } catch (RemoteException ex) { - } - } - } - } - - public void setIcon(String slot, String iconPackage, int iconId, int iconLevel, - String contentDescription) { - enforceStatusBar(); - - synchronized (mIcons) { - int index = mIcons.getSlotIndex(slot); - if (index < 0) { - throw new SecurityException("invalid status bar icon slot: " + slot); - } - - StatusBarIcon icon = new StatusBarIcon(iconPackage, UserHandle.OWNER, iconId, - iconLevel, 0, - contentDescription); - //Slog.d(TAG, "setIcon slot=" + slot + " index=" + index + " icon=" + icon); - mIcons.setIcon(index, icon); - - if (mBar != null) { - try { - mBar.setIcon(index, icon); - } catch (RemoteException ex) { - } - } - } - } - - public void setIconVisibility(String slot, boolean visible) { - enforceStatusBar(); - - synchronized (mIcons) { - int index = mIcons.getSlotIndex(slot); - if (index < 0) { - throw new SecurityException("invalid status bar icon slot: " + slot); - } - - StatusBarIcon icon = mIcons.getIcon(index); - if (icon == null) { - return; - } - - if (icon.visible != visible) { - icon.visible = visible; - - if (mBar != null) { - try { - mBar.setIcon(index, icon); - } catch (RemoteException ex) { - } - } - } - } - } - - public void removeIcon(String slot) { - enforceStatusBar(); - - synchronized (mIcons) { - int index = mIcons.getSlotIndex(slot); - if (index < 0) { - throw new SecurityException("invalid status bar icon slot: " + slot); - } - - mIcons.removeIcon(index); - - if (mBar != null) { - try { - mBar.removeIcon(index); - } catch (RemoteException ex) { - } - } - } - } - - /** - * Hide or show the on-screen Menu key. Only call this from the window manager, typically in - * response to a window with FLAG_NEEDS_MENU_KEY set. - */ - public void topAppWindowChanged(final boolean menuVisible) { - enforceStatusBar(); - - if (SPEW) Slog.d(TAG, (menuVisible?"showing":"hiding") + " MENU key"); - - synchronized(mLock) { - mMenuVisible = menuVisible; - mHandler.post(new Runnable() { - public void run() { - if (mBar != null) { - try { - mBar.topAppWindowChanged(menuVisible); - } catch (RemoteException ex) { - } - } - } - }); - } - } - - public void setImeWindowStatus(final IBinder token, final int vis, final int backDisposition) { - enforceStatusBar(); - - if (SPEW) { - Slog.d(TAG, "swetImeWindowStatus vis=" + vis + " backDisposition=" + backDisposition); - } - - synchronized(mLock) { - // In case of IME change, we need to call up setImeWindowStatus() regardless of - // mImeWindowVis because mImeWindowVis may not have been set to false when the - // previous IME was destroyed. - mImeWindowVis = vis; - mImeBackDisposition = backDisposition; - mImeToken = token; - mHandler.post(new Runnable() { - public void run() { - if (mBar != null) { - try { - mBar.setImeWindowStatus(token, vis, backDisposition); - } catch (RemoteException ex) { - } - } - } - }); - } - } - - public void setSystemUiVisibility(int vis, int mask) { - // also allows calls from window manager which is in this process. - enforceStatusBarService(); - - if (SPEW) Slog.d(TAG, "setSystemUiVisibility(0x" + Integer.toHexString(vis) + ")"); - - synchronized (mLock) { - updateUiVisibilityLocked(vis, mask); - disableLocked( - mCurrentUserId, - vis & StatusBarManager.DISABLE_MASK, - mSysUiVisToken, - "WindowManager.LayoutParams"); - } - } - - private void updateUiVisibilityLocked(final int vis, final int mask) { - if (mSystemUiVisibility != vis) { - mSystemUiVisibility = vis; - mHandler.post(new Runnable() { - public void run() { - if (mBar != null) { - try { - mBar.setSystemUiVisibility(vis, mask); - } catch (RemoteException ex) { - } - } - } - }); - } - } - - public void setHardKeyboardEnabled(final boolean enabled) { - mHandler.post(new Runnable() { - public void run() { - mWindowManager.setHardKeyboardEnabled(enabled); - } - }); - } - - @Override - public void onHardKeyboardStatusChange(final boolean available, final boolean enabled) { - mHandler.post(new Runnable() { - public void run() { - if (mBar != null) { - try { - mBar.setHardKeyboardStatus(available, enabled); - } catch (RemoteException ex) { - } - } - } - }); - } - - @Override - public void toggleRecentApps() { - if (mBar != null) { - try { - mBar.toggleRecentApps(); - } catch (RemoteException ex) {} - } - } - - @Override - public void preloadRecentApps() { - if (mBar != null) { - try { - mBar.preloadRecentApps(); - } catch (RemoteException ex) {} - } - } - - @Override - public void cancelPreloadRecentApps() { - if (mBar != null) { - try { - mBar.cancelPreloadRecentApps(); - } catch (RemoteException ex) {} - } - } - - @Override - public void setCurrentUser(int newUserId) { - if (SPEW) Slog.d(TAG, "Setting current user to user " + newUserId); - mCurrentUserId = newUserId; - } - - @Override - public void setWindowState(int window, int state) { - if (mBar != null) { - try { - mBar.setWindowState(window, state); - } catch (RemoteException ex) {} - } - } - - private void enforceStatusBar() { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR, - "StatusBarManagerService"); - } - - private void enforceExpandStatusBar() { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.EXPAND_STATUS_BAR, - "StatusBarManagerService"); - } - - private void enforceStatusBarService() { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE, - "StatusBarManagerService"); - } - - // ================================================================================ - // Callbacks from the status bar service. - // ================================================================================ - public void registerStatusBar(IStatusBar bar, StatusBarIconList iconList, - List notificationKeys, List notifications, - int switches[], List binders) { - enforceStatusBarService(); - - Slog.i(TAG, "registerStatusBar bar=" + bar); - mBar = bar; - synchronized (mIcons) { - iconList.copyFrom(mIcons); - } - synchronized (mNotifications) { - for (Map.Entry e: mNotifications.entrySet()) { - notificationKeys.add(e.getKey()); - notifications.add(e.getValue()); - } - } - synchronized (mLock) { - switches[0] = gatherDisableActionsLocked(mCurrentUserId); - switches[1] = mSystemUiVisibility; - switches[2] = mMenuVisible ? 1 : 0; - switches[3] = mImeWindowVis; - switches[4] = mImeBackDisposition; - binders.add(mImeToken); - } - switches[5] = mWindowManager.isHardKeyboardAvailable() ? 1 : 0; - switches[6] = mWindowManager.isHardKeyboardEnabled() ? 1 : 0; - } - - /** - * The status bar service should call this each time the user brings the panel from - * invisible to visible in order to clear the notification light. - */ - public void onPanelRevealed() { - enforceStatusBarService(); - - // tell the notification manager to turn off the lights. - mNotificationCallbacks.onPanelRevealed(); - } - - public void onNotificationClick(String pkg, String tag, int id) { - enforceStatusBarService(); - - mNotificationCallbacks.onNotificationClick(pkg, tag, id); - } - - public void onNotificationError(String pkg, String tag, int id, - int uid, int initialPid, String message) { - enforceStatusBarService(); - - // WARNING: this will call back into us to do the remove. Don't hold any locks. - mNotificationCallbacks.onNotificationError(pkg, tag, id, uid, initialPid, message); - } - - public void onNotificationClear(String pkg, String tag, int id) { - enforceStatusBarService(); - - mNotificationCallbacks.onNotificationClear(pkg, tag, id); - } - - public void onClearAllNotifications() { - enforceStatusBarService(); - - mNotificationCallbacks.onClearAll(); - } - - // ================================================================================ - // Callbacks for NotificationManagerService. - // ================================================================================ - public IBinder addNotification(StatusBarNotification notification) { - synchronized (mNotifications) { - IBinder key = new Binder(); - mNotifications.put(key, notification); - if (mBar != null) { - try { - mBar.addNotification(key, notification); - } catch (RemoteException ex) { - } - } - return key; - } - } - - public void updateNotification(IBinder key, StatusBarNotification notification) { - synchronized (mNotifications) { - if (!mNotifications.containsKey(key)) { - throw new IllegalArgumentException("updateNotification key not found: " + key); - } - mNotifications.put(key, notification); - if (mBar != null) { - try { - mBar.updateNotification(key, notification); - } catch (RemoteException ex) { - } - } - } - } - - public void removeNotification(IBinder key) { - synchronized (mNotifications) { - final StatusBarNotification n = mNotifications.remove(key); - if (n == null) { - Slog.e(TAG, "removeNotification key not found: " + key); - return; - } - if (mBar != null) { - try { - mBar.removeNotification(key); - } catch (RemoteException ex) { - } - } - } - } - - // ================================================================================ - // Can be called from any thread - // ================================================================================ - - // lock on mDisableRecords - void manageDisableListLocked(int userId, int what, IBinder token, String pkg) { - if (SPEW) { - Slog.d(TAG, "manageDisableList userId=" + userId - + " what=0x" + Integer.toHexString(what) + " pkg=" + pkg); - } - // update the list - final int N = mDisableRecords.size(); - DisableRecord tok = null; - int i; - for (i=0; i e: mNotifications.entrySet()) { - pw.printf(" %2d: %s\n", i, e.getValue().toString()); - i++; - } - } - - synchronized (mLock) { - pw.println(" mDisabled=0x" + Integer.toHexString(mDisabled)); - final int N = mDisableRecords.size(); - pw.println(" mDisableRecords.size=" + N); - for (int i=0; i void publishLocalService(Class type, T service) { + LocalServices.addService(type, service); + } + + /** + * Get a local service by interface. + */ + protected final T getLocalService(Class type) { + return LocalServices.getService(type); + } + + public final Context getContext() { + return mContext; + } + +// /** +// * Called when a new user has been created. If your service deals with multiple users, this +// * method should be overridden. +// * +// * @param userHandle The user that was created. +// */ +// public void onUserCreated(int userHandle) { +// } +// +// /** +// * Called when an existing user has started a new session. If your service deals with multiple +// * users, this method should be overridden. +// * +// * @param userHandle The user who started a new session. +// */ +// public void onUserStarted(int userHandle) { +// } +// +// /** +// * Called when a background user session has entered the foreground. If your service deals with +// * multiple users, this method should be overridden. +// * +// * @param userHandle The user who's session entered the foreground. +// */ +// public void onUserForeground(int userHandle) { +// } +// +// /** +// * Called when a foreground user session has entered the background. If your service deals with +// * multiple users, this method should be overridden; +// * +// * @param userHandle The user who's session entered the background. +// */ +// public void onUserBackground(int userHandle) { +// } +// +// /** +// * Called when a user's active session has stopped. If your service deals with multiple users, +// * this method should be overridden. +// * +// * @param userHandle The user who's session has stopped. +// */ +// public void onUserStopped(int userHandle) { +// } +// +// /** +// * Called when a user has been removed from the system. If your service deals with multiple +// * users, this method should be overridden. +// * +// * @param userHandle The user who has been removed. +// */ +// public void onUserRemoved(int userHandle) { +// } +} diff --git a/services/java/com/android/server/SystemServiceManager.java b/services/java/com/android/server/SystemServiceManager.java new file mode 100644 index 000000000000..648975a9b7ca --- /dev/null +++ b/services/java/com/android/server/SystemServiceManager.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2013 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 android.content.Context; +import android.util.Log; +import android.util.Slog; + +import java.util.ArrayList; + +/** + * Manages creating, starting, and other lifecycle events of system services. + */ +public class SystemServiceManager { + private static final String TAG = "SystemServiceManager"; + + private final Context mContext; + + // Services that should receive lifecycle events. + private final ArrayList mServices = new ArrayList(); + + private int mCurrentPhase = -1; + + public SystemServiceManager(Context context) { + mContext = context; + } + + /** + * Creates and starts a system service. The class must be a subclass of + * {@link com.android.server.SystemService}. + * + * @param serviceClass A Java class that implements the SystemService interface. + * @throws RuntimeException if the service fails to start. + */ + public void startService(Class serviceClass) { + final SystemService serviceInstance = createInstance(serviceClass); + try { + Slog.i(TAG, "Creating " + serviceClass.getSimpleName()); + serviceInstance.init(mContext, this); + } catch (Throwable e) { + throw new RuntimeException("Failed to create service " + serviceClass.getName(), e); + } + + mServices.add(serviceInstance); + + try { + Slog.i(TAG, "Starting " + serviceClass.getSimpleName()); + serviceInstance.onStart(); + } catch (Throwable e) { + throw new RuntimeException("Failed to start service " + serviceClass.getName(), e); + } + } + + /** + * Starts the specified boot phase for all system services that have been started up to + * this point. + * + * @param phase The boot phase to start. + */ + public void startBootPhase(final int phase) { + if (phase <= mCurrentPhase) { + throw new IllegalArgumentException("Next phase must be larger than previous"); + } + mCurrentPhase = phase; + + Slog.i(TAG, "Starting phase " + mCurrentPhase); + + final int serviceLen = mServices.size(); + for (int i = 0; i < serviceLen; i++) { + final SystemService service = mServices.get(i); + try { + service.onBootPhase(mCurrentPhase); + } catch (Throwable e) { + reportWtf("Service " + service.getClass().getName() + + " threw an Exception processing boot phase " + mCurrentPhase, e); + } + } + } + + /** + * Outputs the state of this manager to the System log. + */ + public void dump() { + StringBuilder builder = new StringBuilder(); + builder.append("Current phase: ").append(mCurrentPhase).append("\n"); + builder.append("Services:\n"); + final int startedLen = mServices.size(); + for (int i = 0; i < startedLen; i++) { + final SystemService service = mServices.get(i); + builder.append("\t") + .append(service.getClass().getSimpleName()) + .append("\n"); + } + + Slog.e(TAG, builder.toString()); + } + + private SystemService createInstance(Class clazz) { + // Make sure it's a type we expect + if (!SystemService.class.isAssignableFrom(clazz)) { + reportWtf("Class " + clazz.getName() + " does not extend " + + SystemService.class.getName()); + } + + try { + return (SystemService) clazz.newInstance(); + } catch (InstantiationException e) { + reportWtf("Class " + clazz.getName() + " is abstract", e); + } catch (IllegalAccessException e) { + reportWtf("Class " + clazz.getName() + + " must have a public no-arg constructor", e); + } + return null; + } + + private static void reportWtf(String message) { + reportWtf(message, null); + } + + private static void reportWtf(String message, Throwable e) { + Slog.i(TAG, "******************************"); + Log.wtf(TAG, message, e); + + // Make sure we die + throw new RuntimeException(message, e); + } +} diff --git a/services/java/com/android/server/TwilightService.java b/services/java/com/android/server/TwilightService.java deleted file mode 100644 index 0356faa610f5..000000000000 --- a/services/java/com/android/server/TwilightService.java +++ /dev/null @@ -1,572 +0,0 @@ -/* - * Copyright (C) 2012 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 android.app.AlarmManager; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.location.Criteria; -import android.location.Location; -import android.location.LocationListener; -import android.location.LocationManager; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.SystemClock; -import android.text.format.DateUtils; -import android.text.format.Time; -import android.util.Slog; - -import java.text.DateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.Iterator; - -import libcore.util.Objects; - -/** - * Figures out whether it's twilight time based on the user's location. - * - * Used by the UI mode manager and other components to adjust night mode - * effects based on sunrise and sunset. - */ -public final class TwilightService { - private static final String TAG = "TwilightService"; - - private static final boolean DEBUG = false; - - private static final String ACTION_UPDATE_TWILIGHT_STATE = - "com.android.server.action.UPDATE_TWILIGHT_STATE"; - - private final Context mContext; - private final AlarmManager mAlarmManager; - private final LocationManager mLocationManager; - private final LocationHandler mLocationHandler; - - private final Object mLock = new Object(); - - private final ArrayList mListeners = - new ArrayList(); - - private boolean mSystemReady; - - private TwilightState mTwilightState; - - public TwilightService(Context context) { - mContext = context; - - mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); - mLocationManager = (LocationManager)mContext.getSystemService(Context.LOCATION_SERVICE); - mLocationHandler = new LocationHandler(); - } - - void systemReady() { - synchronized (mLock) { - mSystemReady = true; - - IntentFilter filter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED); - filter.addAction(Intent.ACTION_TIME_CHANGED); - filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); - filter.addAction(ACTION_UPDATE_TWILIGHT_STATE); - mContext.registerReceiver(mUpdateLocationReceiver, filter); - - if (!mListeners.isEmpty()) { - mLocationHandler.enableLocationUpdates(); - } - } - } - - /** - * Gets the current twilight state. - * - * @return The current twilight state, or null if no information is available. - */ - public TwilightState getCurrentState() { - synchronized (mLock) { - return mTwilightState; - } - } - - /** - * Listens for twilight time. - * - * @param listener The listener. - * @param handler The handler on which to post calls into the listener. - */ - public void registerListener(TwilightListener listener, Handler handler) { - synchronized (mLock) { - mListeners.add(new TwilightListenerRecord(listener, handler)); - - if (mSystemReady && mListeners.size() == 1) { - mLocationHandler.enableLocationUpdates(); - } - } - } - - private void setTwilightState(TwilightState state) { - synchronized (mLock) { - if (!Objects.equal(mTwilightState, state)) { - if (DEBUG) { - Slog.d(TAG, "Twilight state changed: " + state); - } - - mTwilightState = state; - int count = mListeners.size(); - for (int i = 0; i < count; i++) { - mListeners.get(i).post(); - } - } - } - } - - // The user has moved if the accuracy circles of the two locations don't overlap. - private static boolean hasMoved(Location from, Location to) { - if (to == null) { - return false; - } - - if (from == null) { - return true; - } - - // if new location is older than the current one, the device hasn't moved. - if (to.getElapsedRealtimeNanos() < from.getElapsedRealtimeNanos()) { - return false; - } - - // Get the distance between the two points. - float distance = from.distanceTo(to); - - // Get the total accuracy radius for both locations. - float totalAccuracy = from.getAccuracy() + to.getAccuracy(); - - // If the distance is greater than the combined accuracy of the two - // points then they can't overlap and hence the user has moved. - return distance >= totalAccuracy; - } - - /** - * Describes whether it is day or night. - * This object is immutable. - */ - public static final class TwilightState { - private final boolean mIsNight; - private final long mYesterdaySunset; - private final long mTodaySunrise; - private final long mTodaySunset; - private final long mTomorrowSunrise; - - TwilightState(boolean isNight, - long yesterdaySunset, - long todaySunrise, long todaySunset, - long tomorrowSunrise) { - mIsNight = isNight; - mYesterdaySunset = yesterdaySunset; - mTodaySunrise = todaySunrise; - mTodaySunset = todaySunset; - mTomorrowSunrise = tomorrowSunrise; - } - - /** - * Returns true if it is currently night time. - */ - public boolean isNight() { - return mIsNight; - } - - /** - * Returns the time of yesterday's sunset in the System.currentTimeMillis() timebase, - * or -1 if the sun never sets. - */ - public long getYesterdaySunset() { - return mYesterdaySunset; - } - - /** - * Returns the time of today's sunrise in the System.currentTimeMillis() timebase, - * or -1 if the sun never rises. - */ - public long getTodaySunrise() { - return mTodaySunrise; - } - - /** - * Returns the time of today's sunset in the System.currentTimeMillis() timebase, - * or -1 if the sun never sets. - */ - public long getTodaySunset() { - return mTodaySunset; - } - - /** - * Returns the time of tomorrow's sunrise in the System.currentTimeMillis() timebase, - * or -1 if the sun never rises. - */ - public long getTomorrowSunrise() { - return mTomorrowSunrise; - } - - @Override - public boolean equals(Object o) { - return o instanceof TwilightState && equals((TwilightState)o); - } - - public boolean equals(TwilightState other) { - return other != null - && mIsNight == other.mIsNight - && mYesterdaySunset == other.mYesterdaySunset - && mTodaySunrise == other.mTodaySunrise - && mTodaySunset == other.mTodaySunset - && mTomorrowSunrise == other.mTomorrowSunrise; - } - - @Override - public int hashCode() { - return 0; // don't care - } - - @Override - public String toString() { - DateFormat f = DateFormat.getDateTimeInstance(); - return "{TwilightState: isNight=" + mIsNight - + ", mYesterdaySunset=" + f.format(new Date(mYesterdaySunset)) - + ", mTodaySunrise=" + f.format(new Date(mTodaySunrise)) - + ", mTodaySunset=" + f.format(new Date(mTodaySunset)) - + ", mTomorrowSunrise=" + f.format(new Date(mTomorrowSunrise)) - + "}"; - } - } - - /** - * Listener for changes in twilight state. - */ - public interface TwilightListener { - public void onTwilightStateChanged(); - } - - private static final class TwilightListenerRecord implements Runnable { - private final TwilightListener mListener; - private final Handler mHandler; - - public TwilightListenerRecord(TwilightListener listener, Handler handler) { - mListener = listener; - mHandler = handler; - } - - public void post() { - mHandler.post(this); - } - - @Override - public void run() { - mListener.onTwilightStateChanged(); - } - } - - private final class LocationHandler extends Handler { - private static final int MSG_ENABLE_LOCATION_UPDATES = 1; - private static final int MSG_GET_NEW_LOCATION_UPDATE = 2; - private static final int MSG_PROCESS_NEW_LOCATION = 3; - private static final int MSG_DO_TWILIGHT_UPDATE = 4; - - private static final long LOCATION_UPDATE_MS = 24 * DateUtils.HOUR_IN_MILLIS; - private static final long MIN_LOCATION_UPDATE_MS = 30 * DateUtils.MINUTE_IN_MILLIS; - private static final float LOCATION_UPDATE_DISTANCE_METER = 1000 * 20; - private static final long LOCATION_UPDATE_ENABLE_INTERVAL_MIN = 5000; - private static final long LOCATION_UPDATE_ENABLE_INTERVAL_MAX = - 15 * DateUtils.MINUTE_IN_MILLIS; - private static final double FACTOR_GMT_OFFSET_LONGITUDE = - 1000.0 * 360.0 / DateUtils.DAY_IN_MILLIS; - - private boolean mPassiveListenerEnabled; - private boolean mNetworkListenerEnabled; - private boolean mDidFirstInit; - private long mLastNetworkRegisterTime = -MIN_LOCATION_UPDATE_MS; - private long mLastUpdateInterval; - private Location mLocation; - private final TwilightCalculator mTwilightCalculator = new TwilightCalculator(); - - public void processNewLocation(Location location) { - Message msg = obtainMessage(MSG_PROCESS_NEW_LOCATION, location); - sendMessage(msg); - } - - public void enableLocationUpdates() { - sendEmptyMessage(MSG_ENABLE_LOCATION_UPDATES); - } - - public void requestLocationUpdate() { - sendEmptyMessage(MSG_GET_NEW_LOCATION_UPDATE); - } - - public void requestTwilightUpdate() { - sendEmptyMessage(MSG_DO_TWILIGHT_UPDATE); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_PROCESS_NEW_LOCATION: { - final Location location = (Location)msg.obj; - final boolean hasMoved = hasMoved(mLocation, location); - final boolean hasBetterAccuracy = mLocation == null - || location.getAccuracy() < mLocation.getAccuracy(); - if (DEBUG) { - Slog.d(TAG, "Processing new location: " + location - + ", hasMoved=" + hasMoved - + ", hasBetterAccuracy=" + hasBetterAccuracy); - } - if (hasMoved || hasBetterAccuracy) { - setLocation(location); - } - break; - } - - case MSG_GET_NEW_LOCATION_UPDATE: - if (!mNetworkListenerEnabled) { - // Don't do anything -- we are still trying to get a - // location. - return; - } - if ((mLastNetworkRegisterTime + MIN_LOCATION_UPDATE_MS) >= - SystemClock.elapsedRealtime()) { - // Don't do anything -- it hasn't been long enough - // since we last requested an update. - return; - } - - // Unregister the current location monitor, so we can - // register a new one for it to get an immediate update. - mNetworkListenerEnabled = false; - mLocationManager.removeUpdates(mEmptyLocationListener); - - // Fall through to re-register listener. - case MSG_ENABLE_LOCATION_UPDATES: - // enable network provider to receive at least location updates for a given - // distance. - boolean networkLocationEnabled; - try { - networkLocationEnabled = - mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER); - } catch (Exception e) { - // we may get IllegalArgumentException if network location provider - // does not exist or is not yet installed. - networkLocationEnabled = false; - } - if (!mNetworkListenerEnabled && networkLocationEnabled) { - mNetworkListenerEnabled = true; - mLastNetworkRegisterTime = SystemClock.elapsedRealtime(); - mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, - LOCATION_UPDATE_MS, 0, mEmptyLocationListener); - - if (!mDidFirstInit) { - mDidFirstInit = true; - if (mLocation == null) { - retrieveLocation(); - } - } - } - - // enable passive provider to receive updates from location fixes (gps - // and network). - boolean passiveLocationEnabled; - try { - passiveLocationEnabled = - mLocationManager.isProviderEnabled(LocationManager.PASSIVE_PROVIDER); - } catch (Exception e) { - // we may get IllegalArgumentException if passive location provider - // does not exist or is not yet installed. - passiveLocationEnabled = false; - } - - if (!mPassiveListenerEnabled && passiveLocationEnabled) { - mPassiveListenerEnabled = true; - mLocationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, - 0, LOCATION_UPDATE_DISTANCE_METER , mLocationListener); - } - - if (!(mNetworkListenerEnabled && mPassiveListenerEnabled)) { - mLastUpdateInterval *= 1.5; - if (mLastUpdateInterval == 0) { - mLastUpdateInterval = LOCATION_UPDATE_ENABLE_INTERVAL_MIN; - } else if (mLastUpdateInterval > LOCATION_UPDATE_ENABLE_INTERVAL_MAX) { - mLastUpdateInterval = LOCATION_UPDATE_ENABLE_INTERVAL_MAX; - } - sendEmptyMessageDelayed(MSG_ENABLE_LOCATION_UPDATES, mLastUpdateInterval); - } - break; - - case MSG_DO_TWILIGHT_UPDATE: - updateTwilightState(); - break; - } - } - - private void retrieveLocation() { - Location location = null; - final Iterator providers = - mLocationManager.getProviders(new Criteria(), true).iterator(); - while (providers.hasNext()) { - final Location lastKnownLocation = - mLocationManager.getLastKnownLocation(providers.next()); - // pick the most recent location - if (location == null || (lastKnownLocation != null && - location.getElapsedRealtimeNanos() < - lastKnownLocation.getElapsedRealtimeNanos())) { - location = lastKnownLocation; - } - } - - // In the case there is no location available (e.g. GPS fix or network location - // is not available yet), the longitude of the location is estimated using the timezone, - // latitude and accuracy are set to get a good average. - if (location == null) { - Time currentTime = new Time(); - currentTime.set(System.currentTimeMillis()); - double lngOffset = FACTOR_GMT_OFFSET_LONGITUDE * - (currentTime.gmtoff - (currentTime.isDst > 0 ? 3600 : 0)); - location = new Location("fake"); - location.setLongitude(lngOffset); - location.setLatitude(0); - location.setAccuracy(417000.0f); - location.setTime(System.currentTimeMillis()); - location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos()); - - if (DEBUG) { - Slog.d(TAG, "Estimated location from timezone: " + location); - } - } - - setLocation(location); - } - - private void setLocation(Location location) { - mLocation = location; - updateTwilightState(); - } - - private void updateTwilightState() { - if (mLocation == null) { - setTwilightState(null); - return; - } - - final long now = System.currentTimeMillis(); - - // calculate yesterday's twilight - mTwilightCalculator.calculateTwilight(now - DateUtils.DAY_IN_MILLIS, - mLocation.getLatitude(), mLocation.getLongitude()); - final long yesterdaySunset = mTwilightCalculator.mSunset; - - // calculate today's twilight - mTwilightCalculator.calculateTwilight(now, - mLocation.getLatitude(), mLocation.getLongitude()); - final boolean isNight = (mTwilightCalculator.mState == TwilightCalculator.NIGHT); - final long todaySunrise = mTwilightCalculator.mSunrise; - final long todaySunset = mTwilightCalculator.mSunset; - - // calculate tomorrow's twilight - mTwilightCalculator.calculateTwilight(now + DateUtils.DAY_IN_MILLIS, - mLocation.getLatitude(), mLocation.getLongitude()); - final long tomorrowSunrise = mTwilightCalculator.mSunrise; - - // set twilight state - TwilightState state = new TwilightState(isNight, yesterdaySunset, - todaySunrise, todaySunset, tomorrowSunrise); - if (DEBUG) { - Slog.d(TAG, "Updating twilight state: " + state); - } - setTwilightState(state); - - // schedule next update - long nextUpdate = 0; - if (todaySunrise == -1 || todaySunset == -1) { - // In the case the day or night never ends the update is scheduled 12 hours later. - nextUpdate = now + 12 * DateUtils.HOUR_IN_MILLIS; - } else { - // add some extra time to be on the safe side. - nextUpdate += DateUtils.MINUTE_IN_MILLIS; - - if (now > todaySunset) { - nextUpdate += tomorrowSunrise; - } else if (now > todaySunrise) { - nextUpdate += todaySunset; - } else { - nextUpdate += todaySunrise; - } - } - - if (DEBUG) { - Slog.d(TAG, "Next update in " + (nextUpdate - now) + " ms"); - } - - Intent updateIntent = new Intent(ACTION_UPDATE_TWILIGHT_STATE); - PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, updateIntent, 0); - mAlarmManager.cancel(pendingIntent); - mAlarmManager.setExact(AlarmManager.RTC, nextUpdate, pendingIntent); - } - }; - - private final BroadcastReceiver mUpdateLocationReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(intent.getAction()) - && !intent.getBooleanExtra("state", false)) { - // Airplane mode is now off! - mLocationHandler.requestLocationUpdate(); - return; - } - - // Time zone has changed or alarm expired. - mLocationHandler.requestTwilightUpdate(); - } - }; - - // A LocationListener to initialize the network location provider. The location updates - // are handled through the passive location provider. - private final LocationListener mEmptyLocationListener = new LocationListener() { - public void onLocationChanged(Location location) { - } - - public void onProviderDisabled(String provider) { - } - - public void onProviderEnabled(String provider) { - } - - public void onStatusChanged(String provider, int status, Bundle extras) { - } - }; - - private final LocationListener mLocationListener = new LocationListener() { - public void onLocationChanged(Location location) { - mLocationHandler.processNewLocation(location); - } - - public void onProviderDisabled(String provider) { - } - - public void onProviderEnabled(String provider) { - } - - public void onStatusChanged(String provider, int status, Bundle extras) { - } - }; -} diff --git a/services/java/com/android/server/UiModeManagerService.java b/services/java/com/android/server/UiModeManagerService.java index 062be01ab26a..de912dcc1332 100644 --- a/services/java/com/android/server/UiModeManagerService.java +++ b/services/java/com/android/server/UiModeManagerService.java @@ -34,9 +34,9 @@ import android.content.res.Configuration; import android.os.BatteryManager; import android.os.Binder; import android.os.Handler; +import android.os.IBinder; import android.os.PowerManager; import android.os.RemoteException; -import android.os.ServiceManager; import android.os.UserHandle; import android.provider.Settings; import android.service.dreams.Sandman; @@ -47,9 +47,11 @@ import java.io.PrintWriter; import com.android.internal.R; import com.android.internal.app.DisableCarModeActivity; -import com.android.server.TwilightService.TwilightState; +import com.android.server.twilight.TwilightListener; +import com.android.server.twilight.TwilightManager; +import com.android.server.twilight.TwilightState; -final class UiModeManagerService extends IUiModeManager.Stub { +final class UiModeManagerService extends SystemService { private static final String TAG = UiModeManager.class.getSimpleName(); private static final boolean LOG = false; @@ -57,40 +59,36 @@ final class UiModeManagerService extends IUiModeManager.Stub { private static final boolean ENABLE_LAUNCH_CAR_DOCK_APP = true; private static final boolean ENABLE_LAUNCH_DESK_DOCK_APP = true; - private final Context mContext; - private final TwilightService mTwilightService; - private final Handler mHandler = new Handler(); - final Object mLock = new Object(); - private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED; + private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED; + int mNightMode = UiModeManager.MODE_NIGHT_NO; - private int mNightMode = UiModeManager.MODE_NIGHT_NO; private boolean mCarModeEnabled = false; private boolean mCharging = false; - private final int mDefaultUiModeType; - private final boolean mCarModeKeepsScreenOn; - private final boolean mDeskModeKeepsScreenOn; - private final boolean mTelevision; - + private int mDefaultUiModeType; + private boolean mCarModeKeepsScreenOn; + private boolean mDeskModeKeepsScreenOn; + private boolean mTelevision; private boolean mComputedNightMode; - private int mCurUiMode = 0; - private int mSetUiMode = 0; + int mCurUiMode = 0; + private int mSetUiMode = 0; private boolean mHoldingConfiguration = false; + private Configuration mConfiguration = new Configuration(); + boolean mSystemReady; - private boolean mSystemReady; + private final Handler mHandler = new Handler(); + private TwilightManager mTwilightManager; private NotificationManager mNotificationManager; - private StatusBarManager mStatusBarManager; - private final PowerManager mPowerManager; - private final PowerManager.WakeLock mWakeLock; + private PowerManager.WakeLock mWakeLock; - static Intent buildHomeIntent(String category) { + private static Intent buildHomeIntent(String category) { Intent intent = new Intent(Intent.ACTION_MAIN); intent.addCategory(category); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK @@ -142,28 +140,26 @@ final class UiModeManagerService extends IUiModeManager.Stub { } }; - private final TwilightService.TwilightListener mTwilightListener = - new TwilightService.TwilightListener() { + private final TwilightListener mTwilightListener = new TwilightListener() { @Override public void onTwilightStateChanged() { updateTwilight(); } }; - public UiModeManagerService(Context context, TwilightService twilight) { - mContext = context; - mTwilightService = twilight; - - ServiceManager.addService(Context.UI_MODE_SERVICE, this); - - mContext.registerReceiver(mDockModeReceiver, + @Override + public void onStart() { + final Context context = getContext(); + mTwilightManager = getLocalService(TwilightManager.class); + final PowerManager powerManager = + (PowerManager) context.getSystemService(Context.POWER_SERVICE); + mWakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG); + + context.registerReceiver(mDockModeReceiver, new IntentFilter(Intent.ACTION_DOCK_EVENT)); - mContext.registerReceiver(mBatteryReceiver, + context.registerReceiver(mBatteryReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); - mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); - mWakeLock = mPowerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG); - mConfiguration.setToDefaults(); mDefaultUiModeType = context.getResources().getInteger( @@ -175,101 +171,139 @@ final class UiModeManagerService extends IUiModeManager.Stub { mTelevision = context.getPackageManager().hasSystemFeature( PackageManager.FEATURE_TELEVISION); - mNightMode = Settings.Secure.getInt(mContext.getContentResolver(), + mNightMode = Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.UI_NIGHT_MODE, UiModeManager.MODE_NIGHT_AUTO); - mTwilightService.registerListener(mTwilightListener, mHandler); + mTwilightManager.registerListener(mTwilightListener, mHandler); + + publishBinderService(Context.UI_MODE_SERVICE, mService); } - @Override // Binder call - public void disableCarMode(int flags) { - final long ident = Binder.clearCallingIdentity(); - try { - synchronized (mLock) { - setCarModeLocked(false); - if (mSystemReady) { - updateLocked(0, flags); + private final IBinder mService = new IUiModeManager.Stub() { + @Override + public void enableCarMode(int flags) { + final long ident = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + setCarModeLocked(true); + if (mSystemReady) { + updateLocked(flags, 0); + } } + } finally { + Binder.restoreCallingIdentity(ident); } - } finally { - Binder.restoreCallingIdentity(ident); } - } - @Override // Binder call - public void enableCarMode(int flags) { - final long ident = Binder.clearCallingIdentity(); - try { - synchronized (mLock) { - setCarModeLocked(true); - if (mSystemReady) { - updateLocked(flags, 0); + @Override + public void disableCarMode(int flags) { + final long ident = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + setCarModeLocked(false); + if (mSystemReady) { + updateLocked(0, flags); + } } + } finally { + Binder.restoreCallingIdentity(ident); } - } finally { - Binder.restoreCallingIdentity(ident); } - } - @Override // Binder call - public int getCurrentModeType() { - final long ident = Binder.clearCallingIdentity(); - try { - synchronized (mLock) { - return mCurUiMode & Configuration.UI_MODE_TYPE_MASK; + @Override + public int getCurrentModeType() { + final long ident = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + return mCurUiMode & Configuration.UI_MODE_TYPE_MASK; + } + } finally { + Binder.restoreCallingIdentity(ident); } - } finally { - Binder.restoreCallingIdentity(ident); } - } - @Override // Binder call - public void setNightMode(int mode) { - switch (mode) { - case UiModeManager.MODE_NIGHT_NO: - case UiModeManager.MODE_NIGHT_YES: - case UiModeManager.MODE_NIGHT_AUTO: - break; - default: - throw new IllegalArgumentException("Unknown mode: " + mode); + @Override + public void setNightMode(int mode) { + switch (mode) { + case UiModeManager.MODE_NIGHT_NO: + case UiModeManager.MODE_NIGHT_YES: + case UiModeManager.MODE_NIGHT_AUTO: + break; + default: + throw new IllegalArgumentException("Unknown mode: " + mode); + } + + final long ident = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + if (isDoingNightModeLocked() && mNightMode != mode) { + Settings.Secure.putInt(getContext().getContentResolver(), + Settings.Secure.UI_NIGHT_MODE, mode); + mNightMode = mode; + updateLocked(0, 0); + } + } + } finally { + Binder.restoreCallingIdentity(ident); + } } - final long ident = Binder.clearCallingIdentity(); - try { + @Override + public int getNightMode() { synchronized (mLock) { - if (isDoingNightModeLocked() && mNightMode != mode) { - Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.UI_NIGHT_MODE, mode); - mNightMode = mode; - updateLocked(0, 0); - } + return mNightMode; } - } finally { - Binder.restoreCallingIdentity(ident); } - } - @Override // Binder call - public int getNightMode() { + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + + pw.println("Permission Denial: can't dump uimode service from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + return; + } + + dumpImpl(pw); + } + }; + + void dumpImpl(PrintWriter pw) { synchronized (mLock) { - return mNightMode; + pw.println("Current UI Mode Service state:"); + pw.print(" mDockState="); pw.print(mDockState); + pw.print(" mLastBroadcastState="); pw.println(mLastBroadcastState); + pw.print(" mNightMode="); pw.print(mNightMode); + pw.print(" mCarModeEnabled="); pw.print(mCarModeEnabled); + pw.print(" mComputedNightMode="); pw.println(mComputedNightMode); + pw.print(" mCurUiMode=0x"); pw.print(Integer.toHexString(mCurUiMode)); + pw.print(" mSetUiMode=0x"); pw.println(Integer.toHexString(mSetUiMode)); + pw.print(" mHoldingConfiguration="); pw.print(mHoldingConfiguration); + pw.print(" mSystemReady="); pw.println(mSystemReady); + pw.print(" mTwilightService.getCurrentState()="); + pw.println(mTwilightManager.getCurrentState()); } } - void systemReady() { - synchronized (mLock) { - mSystemReady = true; - mCarModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR; - updateComputedNightModeLocked(); - updateLocked(0, 0); + @Override + public void onBootPhase(int phase) { + if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { + synchronized (mLock) { + mSystemReady = true; + mCarModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR; + updateComputedNightModeLocked(); + updateLocked(0, 0); + } } } - private boolean isDoingNightModeLocked() { + boolean isDoingNightModeLocked() { return mCarModeEnabled || mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED; } - private void setCarModeLocked(boolean enabled) { + void setCarModeLocked(boolean enabled) { if (mCarModeEnabled != enabled) { mCarModeEnabled = enabled; } @@ -344,7 +378,7 @@ final class UiModeManagerService extends IUiModeManager.Stub { } } - private void updateLocked(int enableFlags, int disableFlags) { + void updateLocked(int enableFlags, int disableFlags) { String action = null; String oldAction = null; if (mLastBroadcastState == Intent.EXTRA_DOCK_STATE_CAR) { @@ -359,7 +393,7 @@ final class UiModeManagerService extends IUiModeManager.Stub { adjustStatusBarCarModeLocked(); if (oldAction != null) { - mContext.sendBroadcastAsUser(new Intent(oldAction), UserHandle.ALL); + getContext().sendBroadcastAsUser(new Intent(oldAction), UserHandle.ALL); } mLastBroadcastState = Intent.EXTRA_DOCK_STATE_CAR; action = UiModeManager.ACTION_ENTER_CAR_MODE; @@ -367,7 +401,7 @@ final class UiModeManagerService extends IUiModeManager.Stub { } else if (isDeskDockState(mDockState)) { if (!isDeskDockState(mLastBroadcastState)) { if (oldAction != null) { - mContext.sendBroadcastAsUser(new Intent(oldAction), UserHandle.ALL); + getContext().sendBroadcastAsUser(new Intent(oldAction), UserHandle.ALL); } mLastBroadcastState = mDockState; action = UiModeManager.ACTION_ENTER_DESK_MODE; @@ -393,7 +427,7 @@ final class UiModeManagerService extends IUiModeManager.Stub { Intent intent = new Intent(action); intent.putExtra("enableFlags", enableFlags); intent.putExtra("disableFlags", disableFlags); - mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null, + getContext().sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null, mResultReceiver, null, Activity.RESULT_OK, null, null); // Attempting to make this transition a little more clean, we are going @@ -491,7 +525,7 @@ final class UiModeManagerService extends IUiModeManager.Stub { // activity manager take care of both the start and config // change. Intent homeIntent = buildHomeIntent(category); - if (Sandman.shouldStartDockApp(mContext, homeIntent)) { + if (Sandman.shouldStartDockApp(getContext(), homeIntent)) { try { int result = ActivityManagerNative.getDefault().startActivityWithConfig( null, null, homeIntent, null, null, null, 0, 0, @@ -513,14 +547,15 @@ final class UiModeManagerService extends IUiModeManager.Stub { // If we did not start a dock app, then start dreaming if supported. if (category != null && !dockAppStarted) { - Sandman.startDreamWhenDockedIfAppropriate(mContext); + Sandman.startDreamWhenDockedIfAppropriate(getContext()); } } private void adjustStatusBarCarModeLocked() { + final Context context = getContext(); if (mStatusBarManager == null) { mStatusBarManager = (StatusBarManager) - mContext.getSystemService(Context.STATUS_BAR_SERVICE); + context.getSystemService(Context.STATUS_BAR_SERVICE); } // Fear not: StatusBarManagerService manages a list of requests to disable @@ -536,12 +571,12 @@ final class UiModeManagerService extends IUiModeManager.Stub { if (mNotificationManager == null) { mNotificationManager = (NotificationManager) - mContext.getSystemService(Context.NOTIFICATION_SERVICE); + context.getSystemService(Context.NOTIFICATION_SERVICE); } if (mNotificationManager != null) { if (mCarModeEnabled) { - Intent carModeOffIntent = new Intent(mContext, DisableCarModeActivity.class); + Intent carModeOffIntent = new Intent(context, DisableCarModeActivity.class); Notification n = new Notification(); n.icon = R.drawable.stat_notify_car_mode; @@ -549,10 +584,10 @@ final class UiModeManagerService extends IUiModeManager.Stub { n.flags = Notification.FLAG_ONGOING_EVENT; n.when = 0; n.setLatestEventInfo( - mContext, - mContext.getString(R.string.car_mode_disable_notification_title), - mContext.getString(R.string.car_mode_disable_notification_message), - PendingIntent.getActivityAsUser(mContext, 0, carModeOffIntent, 0, + context, + context.getString(R.string.car_mode_disable_notification_title), + context.getString(R.string.car_mode_disable_notification_message), + PendingIntent.getActivityAsUser(context, 0, carModeOffIntent, 0, null, UserHandle.CURRENT)); mNotificationManager.notifyAsUser(null, R.string.car_mode_disable_notification_title, n, UserHandle.ALL); @@ -563,7 +598,7 @@ final class UiModeManagerService extends IUiModeManager.Stub { } } - private void updateTwilight() { + void updateTwilight() { synchronized (mLock) { if (isDoingNightModeLocked() && mNightMode == UiModeManager.MODE_NIGHT_AUTO) { updateComputedNightModeLocked(); @@ -573,36 +608,11 @@ final class UiModeManagerService extends IUiModeManager.Stub { } private void updateComputedNightModeLocked() { - TwilightState state = mTwilightService.getCurrentState(); + TwilightState state = mTwilightManager.getCurrentState(); if (state != null) { mComputedNightMode = state.isNight(); } } - @Override - protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) - != PackageManager.PERMISSION_GRANTED) { - pw.println("Permission Denial: can't dump uimode service from from pid=" - + Binder.getCallingPid() - + ", uid=" + Binder.getCallingUid()); - return; - } - - synchronized (mLock) { - pw.println("Current UI Mode Service state:"); - pw.print(" mDockState="); pw.print(mDockState); - pw.print(" mLastBroadcastState="); pw.println(mLastBroadcastState); - pw.print(" mNightMode="); pw.print(mNightMode); - pw.print(" mCarModeEnabled="); pw.print(mCarModeEnabled); - pw.print(" mComputedNightMode="); pw.println(mComputedNightMode); - pw.print(" mCurUiMode=0x"); pw.print(Integer.toHexString(mCurUiMode)); - pw.print(" mSetUiMode=0x"); pw.println(Integer.toHexString(mSetUiMode)); - pw.print(" mHoldingConfiguration="); pw.print(mHoldingConfiguration); - pw.print(" mSystemReady="); pw.println(mSystemReady); - pw.print(" mTwilightService.getCurrentState()="); - pw.println(mTwilightService.getCurrentState()); - } - } } diff --git a/services/java/com/android/server/Watchdog.java b/services/java/com/android/server/Watchdog.java index 3e90078ac87b..e0d650593d09 100644 --- a/services/java/com/android/server/Watchdog.java +++ b/services/java/com/android/server/Watchdog.java @@ -76,9 +76,6 @@ public class Watchdog extends Thread { final ArrayList mHandlerCheckers = new ArrayList(); final HandlerChecker mMonitorChecker; ContentResolver mResolver; - BatteryService mBattery; - PowerManagerService mPower; - AlarmManagerService mAlarm; ActivityManagerService mActivity; int mPhonePid; @@ -230,13 +227,8 @@ public class Watchdog extends Thread { "i/o thread", DEFAULT_TIMEOUT)); } - public void init(Context context, BatteryService battery, - PowerManagerService power, AlarmManagerService alarm, - ActivityManagerService activity) { + public void init(Context context, ActivityManagerService activity) { mResolver = context.getContentResolver(); - mBattery = battery; - mPower = power; - mAlarm = alarm; mActivity = activity; context.registerReceiver(new RebootRequestReceiver(), diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java index 80e6e9497ece..cb04835dce53 100644 --- a/services/java/com/android/server/am/ServiceRecord.java +++ b/services/java/com/android/server/am/ServiceRecord.java @@ -18,7 +18,8 @@ package com.android.server.am; import com.android.internal.app.ProcessStats; import com.android.internal.os.BatteryStatsImpl; -import com.android.server.NotificationManagerService; +import com.android.server.LocalServices; +import com.android.server.notification.NotificationManagerInternal; import android.app.INotificationManager; import android.app.Notification; @@ -427,8 +428,8 @@ final class ServiceRecord extends Binder { final Notification localForegroundNoti = foregroundNoti; ams.mHandler.post(new Runnable() { public void run() { - NotificationManagerService nm = - (NotificationManagerService) NotificationManager.getService(); + NotificationManagerInternal nm = LocalServices.getService( + NotificationManagerInternal.class); if (nm == null) { return; } @@ -479,7 +480,7 @@ final class ServiceRecord extends Binder { throw new RuntimeException("icon must be non-zero"); } int[] outId = new int[1]; - nm.enqueueNotificationInternal(localPackageName, localPackageName, + nm.enqueueNotification(localPackageName, localPackageName, appUid, appPid, null, localForegroundId, localForegroundNoti, outId, userId); } catch (RuntimeException e) { diff --git a/services/java/com/android/server/lights/Light.java b/services/java/com/android/server/lights/Light.java new file mode 100644 index 000000000000..b496b4c6ce8c --- /dev/null +++ b/services/java/com/android/server/lights/Light.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2013 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.lights; + +public abstract class Light { + public static final int LIGHT_FLASH_NONE = 0; + public static final int LIGHT_FLASH_TIMED = 1; + public static final int LIGHT_FLASH_HARDWARE = 2; + + /** + * Light brightness is managed by a user setting. + */ + public static final int BRIGHTNESS_MODE_USER = 0; + + /** + * Light brightness is managed by a light sensor. + */ + public static final int BRIGHTNESS_MODE_SENSOR = 1; + + public abstract void setBrightness(int brightness); + public abstract void setBrightness(int brightness, int brightnessMode); + public abstract void setColor(int color); + public abstract void setFlashing(int color, int mode, int onMS, int offMS); + public abstract void pulse(); + public abstract void pulse(int color, int onMS); + public abstract void turnOff(); +} \ No newline at end of file diff --git a/services/java/com/android/server/lights/LightsManager.java b/services/java/com/android/server/lights/LightsManager.java new file mode 100644 index 000000000000..2f20509dddf5 --- /dev/null +++ b/services/java/com/android/server/lights/LightsManager.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2013 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.lights; + +public abstract class LightsManager { + public static final int LIGHT_ID_BACKLIGHT = 0; + public static final int LIGHT_ID_KEYBOARD = 1; + public static final int LIGHT_ID_BUTTONS = 2; + public static final int LIGHT_ID_BATTERY = 3; + public static final int LIGHT_ID_NOTIFICATIONS = 4; + public static final int LIGHT_ID_ATTENTION = 5; + public static final int LIGHT_ID_BLUETOOTH = 6; + public static final int LIGHT_ID_WIFI = 7; + public static final int LIGHT_ID_COUNT = 8; + + public abstract Light getLight(int id); +} diff --git a/services/java/com/android/server/lights/LightsService.java b/services/java/com/android/server/lights/LightsService.java new file mode 100644 index 000000000000..d81478500a9f --- /dev/null +++ b/services/java/com/android/server/lights/LightsService.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2008 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.lights; + +import com.android.server.SystemService; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Handler; +import android.os.IHardwareService; +import android.os.Message; +import android.util.Slog; + +import java.io.FileInputStream; +import java.io.FileOutputStream; + +public class LightsService extends SystemService { + static final String TAG = "LightsService"; + static final boolean DEBUG = false; + + final LightImpl mLights[] = new LightImpl[LightsManager.LIGHT_ID_COUNT]; + + private final class LightImpl extends Light { + + private LightImpl(int id) { + mId = id; + } + + @Override + public void setBrightness(int brightness) { + setBrightness(brightness, BRIGHTNESS_MODE_USER); + } + + @Override + public void setBrightness(int brightness, int brightnessMode) { + synchronized (this) { + int color = brightness & 0x000000ff; + color = 0xff000000 | (color << 16) | (color << 8) | color; + setLightLocked(color, LIGHT_FLASH_NONE, 0, 0, brightnessMode); + } + } + + @Override + public void setColor(int color) { + synchronized (this) { + setLightLocked(color, LIGHT_FLASH_NONE, 0, 0, 0); + } + } + + @Override + public void setFlashing(int color, int mode, int onMS, int offMS) { + synchronized (this) { + setLightLocked(color, mode, onMS, offMS, BRIGHTNESS_MODE_USER); + } + } + + @Override + public void pulse() { + pulse(0x00ffffff, 7); + } + + @Override + public void pulse(int color, int onMS) { + synchronized (this) { + if (mColor == 0 && !mFlashing) { + setLightLocked(color, LIGHT_FLASH_HARDWARE, onMS, 1000, BRIGHTNESS_MODE_USER); + mH.sendMessageDelayed(Message.obtain(mH, 1, this), onMS); + } + } + } + + @Override + public void turnOff() { + synchronized (this) { + setLightLocked(0, LIGHT_FLASH_NONE, 0, 0, 0); + } + } + + private void stopFlashing() { + synchronized (this) { + setLightLocked(mColor, LIGHT_FLASH_NONE, 0, 0, BRIGHTNESS_MODE_USER); + } + } + + private void setLightLocked(int color, int mode, int onMS, int offMS, int brightnessMode) { + if (color != mColor || mode != mMode || onMS != mOnMS || offMS != mOffMS) { + if (DEBUG) Slog.v(TAG, "setLight #" + mId + ": color=#" + + Integer.toHexString(color)); + mColor = color; + mMode = mode; + mOnMS = onMS; + mOffMS = offMS; + setLight_native(mNativePointer, mId, color, mode, onMS, offMS, brightnessMode); + } + } + + private int mId; + private int mColor; + private int mMode; + private int mOnMS; + private int mOffMS; + private boolean mFlashing; + } + + /* This class implements an obsolete API that was removed after eclair and re-added during the + * final moments of the froyo release to support flashlight apps that had been using the private + * IHardwareService API. This is expected to go away in the next release. + */ + private final IHardwareService.Stub mLegacyFlashlightHack = new IHardwareService.Stub() { + + private static final String FLASHLIGHT_FILE = "/sys/class/leds/spotlight/brightness"; + + public boolean getFlashlightEnabled() { + try { + FileInputStream fis = new FileInputStream(FLASHLIGHT_FILE); + int result = fis.read(); + fis.close(); + return (result != '0'); + } catch (Exception e) { + return false; + } + } + + public void setFlashlightEnabled(boolean on) { + final Context context = getContext(); + if (context.checkCallingOrSelfPermission(android.Manifest.permission.FLASHLIGHT) + != PackageManager.PERMISSION_GRANTED && + context.checkCallingOrSelfPermission(android.Manifest.permission.HARDWARE_TEST) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires FLASHLIGHT or HARDWARE_TEST permission"); + } + try { + FileOutputStream fos = new FileOutputStream(FLASHLIGHT_FILE); + byte[] bytes = new byte[2]; + bytes[0] = (byte)(on ? '1' : '0'); + bytes[1] = '\n'; + fos.write(bytes); + fos.close(); + } catch (Exception e) { + // fail silently + } + } + }; + + @Override + public void onCreate(Context context) { + mNativePointer = init_native(); + + for (int i = 0; i < LightsManager.LIGHT_ID_COUNT; i++) { + mLights[i] = new LightImpl(i); + } + } + + @Override + public void onStart() { + publishBinderService("hardware", mLegacyFlashlightHack); + publishLocalService(LightsManager.class, mService); + } + + private final LightsManager mService = new LightsManager() { + @Override + public com.android.server.lights.Light getLight(int id) { + if (id < LIGHT_ID_COUNT) { + return mLights[id]; + } else { + return null; + } + } + }; + + protected void finalize() throws Throwable { + finalize_native(mNativePointer); + super.finalize(); + } + + private Handler mH = new Handler() { + @Override + public void handleMessage(Message msg) { + LightImpl light = (LightImpl)msg.obj; + light.stopFlashing(); + } + }; + + private static native int init_native(); + private static native void finalize_native(int ptr); + + static native void setLight_native(int ptr, int light, int color, int mode, + int onMS, int offMS, int brightnessMode); + + int mNativePointer; +} diff --git a/services/java/com/android/server/notification/NotificationDelegate.java b/services/java/com/android/server/notification/NotificationDelegate.java new file mode 100644 index 000000000000..df2aaca49eca --- /dev/null +++ b/services/java/com/android/server/notification/NotificationDelegate.java @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2013, 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.notification; + +public interface NotificationDelegate { + void onSetDisabled(int status); + void onClearAll(); + void onNotificationClick(String pkg, String tag, int id); + void onNotificationClear(String pkg, String tag, int id); + void onNotificationError(String pkg, String tag, int id, + int uid, int initialPid, String message); + void onPanelRevealed(); +} diff --git a/services/java/com/android/server/notification/NotificationManagerInternal.java b/services/java/com/android/server/notification/NotificationManagerInternal.java new file mode 100644 index 000000000000..92ffdccb50f0 --- /dev/null +++ b/services/java/com/android/server/notification/NotificationManagerInternal.java @@ -0,0 +1,8 @@ +package com.android.server.notification; + +import android.app.Notification; + +public interface NotificationManagerInternal { + void enqueueNotification(String pkg, String basePkg, int callingUid, int callingPid, + String tag, int id, Notification notification, int[] idReceived, int userId); +} diff --git a/services/java/com/android/server/notification/NotificationManagerService.java b/services/java/com/android/server/notification/NotificationManagerService.java new file mode 100644 index 000000000000..db4cf31d7dad --- /dev/null +++ b/services/java/com/android/server/notification/NotificationManagerService.java @@ -0,0 +1,2427 @@ +/* + * Copyright (C) 2007 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.notification; + +import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; +import static org.xmlpull.v1.XmlPullParser.END_TAG; +import static org.xmlpull.v1.XmlPullParser.START_TAG; + +import android.app.ActivityManager; +import android.app.ActivityManagerNative; +import android.app.AppGlobals; +import android.app.AppOpsManager; +import android.app.IActivityManager; +import android.app.INotificationManager; +import android.app.ITransientNotification; +import android.app.Notification; +import android.app.PendingIntent; +import android.app.StatusBarManager; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.graphics.Bitmap; +import android.media.AudioManager; +import android.media.IRingtonePlayer; +import android.net.Uri; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.Process; +import android.os.RemoteException; +import android.os.UserHandle; +import android.os.Vibrator; +import android.provider.Settings; +import android.service.notification.INotificationListener; +import android.service.notification.NotificationListenerService; +import android.service.notification.StatusBarNotification; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.AtomicFile; +import android.util.EventLog; +import android.util.Log; +import android.util.Slog; +import android.util.Xml; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; +import android.widget.Toast; + +import com.android.internal.R; + +import com.android.internal.notification.NotificationScorer; +import com.android.server.EventLogTags; +import com.android.server.statusbar.StatusBarManagerInternal; +import com.android.server.SystemService; +import com.android.server.lights.Light; +import com.android.server.lights.LightsManager; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.reflect.Array; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Set; + +import libcore.io.IoUtils; + +/** {@hide} */ +public class NotificationManagerService extends SystemService { + static final String TAG = "NotificationService"; + static final boolean DBG = false; + + static final int MAX_PACKAGE_NOTIFICATIONS = 50; + + // message codes + static final int MESSAGE_TIMEOUT = 2; + + static final int LONG_DELAY = 3500; // 3.5 seconds + static final int SHORT_DELAY = 2000; // 2 seconds + + static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250}; + static final int VIBRATE_PATTERN_MAXLEN = 8 * 2 + 1; // up to eight bumps + + static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_NOTIFICATION; + static final boolean SCORE_ONGOING_HIGHER = false; + + static final int JUNK_SCORE = -1000; + static final int NOTIFICATION_PRIORITY_MULTIPLIER = 10; + static final int SCORE_DISPLAY_THRESHOLD = Notification.PRIORITY_MIN * NOTIFICATION_PRIORITY_MULTIPLIER; + + // Notifications with scores below this will not interrupt the user, either via LED or + // sound or vibration + static final int SCORE_INTERRUPTION_THRESHOLD = + Notification.PRIORITY_LOW * NOTIFICATION_PRIORITY_MULTIPLIER; + + static final boolean ENABLE_BLOCKED_NOTIFICATIONS = true; + static final boolean ENABLE_BLOCKED_TOASTS = true; + + static final String ENABLED_NOTIFICATION_LISTENERS_SEPARATOR = ":"; + + private IActivityManager mAm; + AudioManager mAudioManager; + StatusBarManagerInternal mStatusBar; + Vibrator mVibrator; + + final IBinder mForegroundToken = new Binder(); + private WorkerHandler mHandler; + + private Light mNotificationLight; + Light mAttentionLight; + private int mDefaultNotificationColor; + private int mDefaultNotificationLedOn; + + private int mDefaultNotificationLedOff; + private long[] mDefaultVibrationPattern; + + private long[] mFallbackVibrationPattern; + boolean mSystemReady; + + int mDisabledNotifications; + NotificationRecord mSoundNotification; + NotificationRecord mVibrateNotification; + + // for enabling and disabling notification pulse behavior + private boolean mScreenOn = true; + private boolean mInCall = false; + private boolean mNotificationPulseEnabled; + + // used as a mutex for access to all active notifications & listeners + final ArrayList mNotificationList = + new ArrayList(); + + final ArrayList mToastQueue = new ArrayList(); + + ArrayList mLights = new ArrayList(); + NotificationRecord mLedNotification; + + private AppOpsManager mAppOps; + + // contains connections to all connected listeners, including app services + // and system listeners + private ArrayList mListeners + = new ArrayList(); + // things that will be put into mListeners as soon as they're ready + private ArrayList mServicesBinding = new ArrayList(); + // lists the component names of all enabled (and therefore connected) listener + // app services for the current user only + private HashSet mEnabledListenersForCurrentUser + = new HashSet(); + // Just the packages from mEnabledListenersForCurrentUser + private HashSet mEnabledListenerPackageNames = new HashSet(); + + // Notification control database. For now just contains disabled packages. + private AtomicFile mPolicyFile; + private HashSet mBlockedPackages = new HashSet(); + + private static final int DB_VERSION = 1; + + private static final String TAG_BODY = "notification-policy"; + private static final String ATTR_VERSION = "version"; + + private static final String TAG_BLOCKED_PKGS = "blocked-packages"; + private static final String TAG_PACKAGE = "package"; + private static final String ATTR_NAME = "name"; + + final ArrayList mScorers = new ArrayList(); + + private class NotificationListenerInfo implements IBinder.DeathRecipient { + INotificationListener listener; + ComponentName component; + int userid; + boolean isSystem; + ServiceConnection connection; + + public NotificationListenerInfo(INotificationListener listener, ComponentName component, + int userid, boolean isSystem) { + this.listener = listener; + this.component = component; + this.userid = userid; + this.isSystem = isSystem; + this.connection = null; + } + + public NotificationListenerInfo(INotificationListener listener, ComponentName component, + int userid, ServiceConnection connection) { + this.listener = listener; + this.component = component; + this.userid = userid; + this.isSystem = false; + this.connection = connection; + } + + boolean enabledAndUserMatches(StatusBarNotification sbn) { + final int nid = sbn.getUserId(); + if (!isEnabledForCurrentUser()) { + return false; + } + if (this.userid == UserHandle.USER_ALL) return true; + return (nid == UserHandle.USER_ALL || nid == this.userid); + } + + public void notifyPostedIfUserMatch(StatusBarNotification sbn) { + if (!enabledAndUserMatches(sbn)) { + return; + } + try { + listener.onNotificationPosted(sbn); + } catch (RemoteException ex) { + Log.e(TAG, "unable to notify listener (posted): " + listener, ex); + } + } + + public void notifyRemovedIfUserMatch(StatusBarNotification sbn) { + if (!enabledAndUserMatches(sbn)) return; + try { + listener.onNotificationRemoved(sbn); + } catch (RemoteException ex) { + Log.e(TAG, "unable to notify listener (removed): " + listener, ex); + } + } + + @Override + public void binderDied() { + if (connection == null) { + // This is not a service; it won't be recreated. We can give up this connection. + unregisterListenerImpl(this.listener, this.userid); + } + } + + /** convenience method for looking in mEnabledListenersForCurrentUser */ + public boolean isEnabledForCurrentUser() { + if (this.isSystem) return true; + if (this.connection == null) return false; + return mEnabledListenersForCurrentUser.contains(this.component); + } + } + + private static class Archive { + static final int BUFFER_SIZE = 250; + ArrayDeque mBuffer = new ArrayDeque(BUFFER_SIZE); + + public Archive() { + } + + public String toString() { + final StringBuilder sb = new StringBuilder(); + final int N = mBuffer.size(); + sb.append("Archive ("); + sb.append(N); + sb.append(" notification"); + sb.append((N==1)?")":"s)"); + return sb.toString(); + } + + public void record(StatusBarNotification nr) { + if (mBuffer.size() == BUFFER_SIZE) { + mBuffer.removeFirst(); + } + + // We don't want to store the heavy bits of the notification in the archive, + // but other clients in the system process might be using the object, so we + // store a (lightened) copy. + mBuffer.addLast(nr.cloneLight()); + } + + + public void clear() { + mBuffer.clear(); + } + + public Iterator descendingIterator() { + return mBuffer.descendingIterator(); + } + public Iterator ascendingIterator() { + return mBuffer.iterator(); + } + public Iterator filter( + final Iterator iter, final String pkg, final int userId) { + return new Iterator() { + StatusBarNotification mNext = findNext(); + + private StatusBarNotification findNext() { + while (iter.hasNext()) { + StatusBarNotification nr = iter.next(); + if ((pkg == null || nr.getPackageName() == pkg) + && (userId == UserHandle.USER_ALL || nr.getUserId() == userId)) { + return nr; + } + } + return null; + } + + @Override + public boolean hasNext() { + return mNext == null; + } + + @Override + public StatusBarNotification next() { + StatusBarNotification next = mNext; + if (next == null) { + throw new NoSuchElementException(); + } + mNext = findNext(); + return next; + } + + @Override + public void remove() { + iter.remove(); + } + }; + } + + public StatusBarNotification[] getArray(int count) { + if (count == 0) count = Archive.BUFFER_SIZE; + final StatusBarNotification[] a + = new StatusBarNotification[Math.min(count, mBuffer.size())]; + Iterator iter = descendingIterator(); + int i=0; + while (iter.hasNext() && i < count) { + a[i++] = iter.next(); + } + return a; + } + + public StatusBarNotification[] getArray(int count, String pkg, int userId) { + if (count == 0) count = Archive.BUFFER_SIZE; + final StatusBarNotification[] a + = new StatusBarNotification[Math.min(count, mBuffer.size())]; + Iterator iter = filter(descendingIterator(), pkg, userId); + int i=0; + while (iter.hasNext() && i < count) { + a[i++] = iter.next(); + } + return a; + } + + } + + Archive mArchive = new Archive(); + + private void loadBlockDb() { + synchronized(mBlockedPackages) { + if (mPolicyFile == null) { + File dir = new File("/data/system"); + mPolicyFile = new AtomicFile(new File(dir, "notification_policy.xml")); + + mBlockedPackages.clear(); + + FileInputStream infile = null; + try { + infile = mPolicyFile.openRead(); + final XmlPullParser parser = Xml.newPullParser(); + parser.setInput(infile, null); + + int type; + String tag; + int version = DB_VERSION; + while ((type = parser.next()) != END_DOCUMENT) { + tag = parser.getName(); + if (type == START_TAG) { + if (TAG_BODY.equals(tag)) { + version = Integer.parseInt( + parser.getAttributeValue(null, ATTR_VERSION)); + } else if (TAG_BLOCKED_PKGS.equals(tag)) { + while ((type = parser.next()) != END_DOCUMENT) { + tag = parser.getName(); + if (TAG_PACKAGE.equals(tag)) { + mBlockedPackages.add( + parser.getAttributeValue(null, ATTR_NAME)); + } else if (TAG_BLOCKED_PKGS.equals(tag) && type == END_TAG) { + break; + } + } + } + } + } + } catch (FileNotFoundException e) { + // No data yet + } catch (IOException e) { + Log.wtf(TAG, "Unable to read blocked notifications database", e); + } catch (NumberFormatException e) { + Log.wtf(TAG, "Unable to parse blocked notifications database", e); + } catch (XmlPullParserException e) { + Log.wtf(TAG, "Unable to parse blocked notifications database", e); + } finally { + IoUtils.closeQuietly(infile); + } + } + } + } + + /** Use this when you actually want to post a notification or toast. + * + * Unchecked. Not exposed via Binder, but can be called in the course of enqueue*(). + */ + private boolean noteNotificationOp(String pkg, int uid) { + if (mAppOps.noteOpNoThrow(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg) + != AppOpsManager.MODE_ALLOWED) { + Slog.v(TAG, "notifications are disabled by AppOps for " + pkg); + return false; + } + return true; + } + + private static String idDebugString(Context baseContext, String packageName, int id) { + Context c = null; + + if (packageName != null) { + try { + c = baseContext.createPackageContext(packageName, 0); + } catch (NameNotFoundException e) { + c = baseContext; + } + } else { + c = baseContext; + } + + String pkg; + String type; + String name; + + Resources r = c.getResources(); + try { + return r.getResourceName(id); + } catch (Resources.NotFoundException e) { + return ""; + } + } + + + /** + * Remove notification access for any services that no longer exist. + */ + void disableNonexistentListeners() { + int currentUser = ActivityManager.getCurrentUser(); + String flatIn = Settings.Secure.getStringForUser( + getContext().getContentResolver(), + Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, + currentUser); + if (!TextUtils.isEmpty(flatIn)) { + if (DBG) Slog.v(TAG, "flat before: " + flatIn); + PackageManager pm = getContext().getPackageManager(); + List installedServices = pm.queryIntentServicesAsUser( + new Intent(NotificationListenerService.SERVICE_INTERFACE), + PackageManager.GET_SERVICES | PackageManager.GET_META_DATA, + currentUser); + + Set installed = new HashSet(); + for (int i = 0, count = installedServices.size(); i < count; i++) { + ResolveInfo resolveInfo = installedServices.get(i); + ServiceInfo info = resolveInfo.serviceInfo; + + if (!android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE.equals( + info.permission)) { + Slog.w(TAG, "Skipping notification listener service " + + info.packageName + "/" + info.name + + ": it does not require the permission " + + android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE); + continue; + } + installed.add(new ComponentName(info.packageName, info.name)); + } + + String flatOut = ""; + if (!installed.isEmpty()) { + String[] enabled = flatIn.split(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR); + ArrayList remaining = new ArrayList(enabled.length); + for (int i = 0; i < enabled.length; i++) { + ComponentName enabledComponent = ComponentName.unflattenFromString(enabled[i]); + if (installed.contains(enabledComponent)) { + remaining.add(enabled[i]); + } + } + flatOut = TextUtils.join(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR, remaining); + } + if (DBG) Slog.v(TAG, "flat after: " + flatOut); + if (!flatIn.equals(flatOut)) { + Settings.Secure.putStringForUser(getContext().getContentResolver(), + Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, + flatOut, currentUser); + } + } + } + + /** + * Called whenever packages change, the user switches, or ENABLED_NOTIFICATION_LISTENERS + * is altered. (For example in response to USER_SWITCHED in our broadcast receiver) + */ + void rebindListenerServices() { + final int currentUser = ActivityManager.getCurrentUser(); + String flat = Settings.Secure.getStringForUser( + getContext().getContentResolver(), + Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, + currentUser); + + NotificationListenerInfo[] toRemove = new NotificationListenerInfo[mListeners.size()]; + final ArrayList toAdd; + + synchronized (mNotificationList) { + // unbind and remove all existing listeners + toRemove = mListeners.toArray(toRemove); + + toAdd = new ArrayList(); + final HashSet newEnabled = new HashSet(); + final HashSet newPackages = new HashSet(); + + // decode the list of components + if (flat != null) { + String[] components = flat.split(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR); + for (int i=0; i=0; i--) { + final NotificationListenerInfo info = mListeners.get(i); + if (name.equals(info.component) + && info.userid == userid) { + // cut old connections + if (DBG) Slog.v(TAG, " disconnecting old listener: " + info.listener); + mListeners.remove(i); + if (info.connection != null) { + getContext().unbindService(info.connection); + } + } + } + + Intent intent = new Intent(NotificationListenerService.SERVICE_INTERFACE); + intent.setComponent(name); + + intent.putExtra(Intent.EXTRA_CLIENT_LABEL, + R.string.notification_listener_binding_label); + + final PendingIntent pendingIntent = PendingIntent.getActivity( + getContext(), 0, new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS), 0); + intent.putExtra(Intent.EXTRA_CLIENT_INTENT, pendingIntent); + + try { + if (DBG) Slog.v(TAG, "binding: " + intent); + if (!getContext().bindServiceAsUser(intent, + new ServiceConnection() { + INotificationListener mListener; + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + synchronized (mNotificationList) { + mServicesBinding.remove(servicesBindingTag); + try { + mListener = INotificationListener.Stub.asInterface(service); + NotificationListenerInfo info + = new NotificationListenerInfo( + mListener, name, userid, this); + service.linkToDeath(info, 0); + mListeners.add(info); + } catch (RemoteException e) { + // already dead + } + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + Slog.v(TAG, "notification listener connection lost: " + name); + } + }, + Context.BIND_AUTO_CREATE, + new UserHandle(userid))) + { + mServicesBinding.remove(servicesBindingTag); + Slog.w(TAG, "Unable to bind listener service: " + intent); + return; + } + } catch (SecurityException ex) { + Slog.e(TAG, "Unable to bind listener service: " + intent, ex); + return; + } + } + } + + + /** + * Remove a listener service for the given user by ComponentName + */ + private void unregisterListenerService(ComponentName name, int userid) { + checkCallerIsSystem(); + + synchronized (mNotificationList) { + final int N = mListeners.size(); + for (int i=N-1; i>=0; i--) { + final NotificationListenerInfo info = mListeners.get(i); + if (name.equals(info.component) + && info.userid == userid) { + mListeners.remove(i); + if (info.connection != null) { + try { + getContext().unbindService(info.connection); + } catch (IllegalArgumentException ex) { + // something happened to the service: we think we have a connection + // but it's bogus. + Slog.e(TAG, "Listener " + name + " could not be unbound: " + ex); + } + } + } + } + } + } + + /** + * asynchronously notify all listeners about a new notification + */ + void notifyPostedLocked(NotificationRecord n) { + // make a copy in case changes are made to the underlying Notification object + final StatusBarNotification sbn = n.sbn.clone(); + for (final NotificationListenerInfo info : mListeners) { + mHandler.post(new Runnable() { + @Override + public void run() { + info.notifyPostedIfUserMatch(sbn); + }}); + } + } + + /** + * asynchronously notify all listeners about a removed notification + */ + void notifyRemovedLocked(NotificationRecord n) { + // make a copy in case changes are made to the underlying Notification object + // NOTE: this copy is lightweight: it doesn't include heavyweight parts of the notification + final StatusBarNotification sbn_light = n.sbn.cloneLight(); + + for (final NotificationListenerInfo info : mListeners) { + mHandler.post(new Runnable() { + @Override + public void run() { + info.notifyRemovedIfUserMatch(sbn_light); + }}); + } + } + + // -- APIs to support listeners clicking/clearing notifications -- + + private NotificationListenerInfo checkListenerToken(INotificationListener listener) { + final IBinder token = listener.asBinder(); + final int N = mListeners.size(); + for (int i=0; i 0) { + pw.println(prefix + " actions={"); + final int N = notification.actions.length; + for (int i=0; i %s", + prefix, + i, + action.title, + action.actionIntent.toString() + )); + } + pw.println(prefix + " }"); + } + if (notification.extras != null && notification.extras.size() > 0) { + pw.println(prefix + " extras={"); + for (String key : notification.extras.keySet()) { + pw.print(prefix + " " + key + "="); + Object val = notification.extras.get(key); + if (val == null) { + pw.println("null"); + } else { + pw.print(val.toString()); + if (val instanceof Bitmap) { + pw.print(String.format(" (%dx%d)", + ((Bitmap) val).getWidth(), + ((Bitmap) val).getHeight())); + } else if (val.getClass().isArray()) { + pw.println(" {"); + final int N = Array.getLength(val); + for (int i=0; i 0) pw.println(","); + pw.print(prefix + " " + Array.get(val, i)); + } + pw.print("\n" + prefix + " }"); + } + pw.println(); + } + } + pw.println(prefix + " }"); + } + } + + @Override + public final String toString() { + return String.format( + "NotificationRecord(0x%08x: pkg=%s user=%s id=%d tag=%s score=%d: %s)", + System.identityHashCode(this), + this.sbn.getPackageName(), this.sbn.getUser(), this.sbn.getId(), + this.sbn.getTag(), this.sbn.getScore(), this.sbn.getNotification()); + } + } + + private static final class ToastRecord + { + final int pid; + final String pkg; + final ITransientNotification callback; + int duration; + + ToastRecord(int pid, String pkg, ITransientNotification callback, int duration) + { + this.pid = pid; + this.pkg = pkg; + this.callback = callback; + this.duration = duration; + } + + void update(int duration) { + this.duration = duration; + } + + void dump(PrintWriter pw, String prefix) { + pw.println(prefix + this); + } + + @Override + public final String toString() + { + return "ToastRecord{" + + Integer.toHexString(System.identityHashCode(this)) + + " pkg=" + pkg + + " callback=" + callback + + " duration=" + duration; + } + } + + private final NotificationDelegate mNotificationDelegate = new NotificationDelegate() { + + @Override + public void onSetDisabled(int status) { + synchronized (mNotificationList) { + mDisabledNotifications = status; + if ((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) { + // cancel whatever's going on + long identity = Binder.clearCallingIdentity(); + try { + final IRingtonePlayer player = mAudioManager.getRingtonePlayer(); + if (player != null) { + player.stopAsync(); + } + } catch (RemoteException e) { + } finally { + Binder.restoreCallingIdentity(identity); + } + + identity = Binder.clearCallingIdentity(); + try { + mVibrator.cancel(); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + } + + @Override + public void onClearAll() { + // XXX to be totally correct, the caller should tell us which user + // this is for. + cancelAll(ActivityManager.getCurrentUser()); + } + + @Override + public void onNotificationClick(String pkg, String tag, int id) { + // XXX to be totally correct, the caller should tell us which user + // this is for. + cancelNotification(pkg, tag, id, Notification.FLAG_AUTO_CANCEL, + Notification.FLAG_FOREGROUND_SERVICE, false, + ActivityManager.getCurrentUser()); + } + + @Override + public void onNotificationClear(String pkg, String tag, int id) { + // XXX to be totally correct, the caller should tell us which user + // this is for. + cancelNotification(pkg, tag, id, 0, + Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE, + true, ActivityManager.getCurrentUser()); + } + + @Override + public void onPanelRevealed() { + synchronized (mNotificationList) { + // sound + mSoundNotification = null; + + long identity = Binder.clearCallingIdentity(); + try { + final IRingtonePlayer player = mAudioManager.getRingtonePlayer(); + if (player != null) { + player.stopAsync(); + } + } catch (RemoteException e) { + } finally { + Binder.restoreCallingIdentity(identity); + } + + // vibrate + mVibrateNotification = null; + identity = Binder.clearCallingIdentity(); + try { + mVibrator.cancel(); + } finally { + Binder.restoreCallingIdentity(identity); + } + + // light + mLights.clear(); + mLedNotification = null; + updateLightsLocked(); + } + } + + @Override + public void onNotificationError(String pkg, String tag, int id, + int uid, int initialPid, String message) { + Slog.d(TAG, "onNotification error pkg=" + pkg + " tag=" + tag + " id=" + id + + "; will crashApplication(uid=" + uid + ", pid=" + initialPid + ")"); + // XXX to be totally correct, the caller should tell us which user + // this is for. + cancelNotification(pkg, tag, id, 0, 0, false, UserHandle.getUserId(uid)); + long ident = Binder.clearCallingIdentity(); + try { + ActivityManagerNative.getDefault().crashApplication(uid, initialPid, pkg, + "Bad notification posted from package " + pkg + + ": " + message); + } catch (RemoteException e) { + } + Binder.restoreCallingIdentity(ident); + } + }; + + private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + + boolean queryRestart = false; + boolean queryRemove = false; + boolean packageChanged = false; + boolean cancelNotifications = true; + + if (action.equals(Intent.ACTION_PACKAGE_ADDED) + || (queryRemove=action.equals(Intent.ACTION_PACKAGE_REMOVED)) + || action.equals(Intent.ACTION_PACKAGE_RESTARTED) + || (packageChanged=action.equals(Intent.ACTION_PACKAGE_CHANGED)) + || (queryRestart=action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART)) + || action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) { + String pkgList[] = null; + boolean queryReplace = queryRemove && + intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); + if (DBG) Slog.i(TAG, "queryReplace=" + queryReplace); + if (action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) { + pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); + } else if (queryRestart) { + pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES); + } else { + Uri uri = intent.getData(); + if (uri == null) { + return; + } + String pkgName = uri.getSchemeSpecificPart(); + if (pkgName == null) { + return; + } + if (packageChanged) { + // We cancel notifications for packages which have just been disabled + try { + final int enabled = getContext().getPackageManager() + .getApplicationEnabledSetting(pkgName); + if (enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED + || enabled == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) { + cancelNotifications = false; + } + } catch (IllegalArgumentException e) { + // Package doesn't exist; probably racing with uninstall. + // cancelNotifications is already true, so nothing to do here. + if (DBG) { + Slog.i(TAG, "Exception trying to look up app enabled setting", e); + } + } + } + pkgList = new String[]{pkgName}; + } + + boolean anyListenersInvolved = false; + if (pkgList != null && (pkgList.length > 0)) { + for (String pkgName : pkgList) { + if (cancelNotifications) { + cancelAllNotificationsInt(pkgName, 0, 0, !queryRestart, + UserHandle.USER_ALL); + } + if (mEnabledListenerPackageNames.contains(pkgName)) { + anyListenersInvolved = true; + } + } + } + + if (anyListenersInvolved) { + // if we're not replacing a package, clean up orphaned bits + if (!queryReplace) { + disableNonexistentListeners(); + } + // make sure we're still bound to any of our + // listeners who may have just upgraded + rebindListenerServices(); + } + } else if (action.equals(Intent.ACTION_SCREEN_ON)) { + // Keep track of screen on/off state, but do not turn off the notification light + // until user passes through the lock screen or views the notification. + mScreenOn = true; + } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { + mScreenOn = false; + } else if (action.equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) { + mInCall = (intent.getStringExtra(TelephonyManager.EXTRA_STATE).equals( + TelephonyManager.EXTRA_STATE_OFFHOOK)); + updateNotificationPulse(); + } else if (action.equals(Intent.ACTION_USER_STOPPED)) { + int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); + if (userHandle >= 0) { + cancelAllNotificationsInt(null, 0, 0, true, userHandle); + } + } else if (action.equals(Intent.ACTION_USER_PRESENT)) { + // turn off LED when user passes through lock screen + mNotificationLight.turnOff(); + } else if (action.equals(Intent.ACTION_USER_SWITCHED)) { + // reload per-user settings + mSettingsObserver.update(null); + } + } + }; + + class SettingsObserver extends ContentObserver { + private final Uri NOTIFICATION_LIGHT_PULSE_URI + = Settings.System.getUriFor(Settings.System.NOTIFICATION_LIGHT_PULSE); + + private final Uri ENABLED_NOTIFICATION_LISTENERS_URI + = Settings.Secure.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS); + + SettingsObserver(Handler handler) { + super(handler); + } + + void observe() { + ContentResolver resolver = getContext().getContentResolver(); + resolver.registerContentObserver(NOTIFICATION_LIGHT_PULSE_URI, + false, this, UserHandle.USER_ALL); + resolver.registerContentObserver(ENABLED_NOTIFICATION_LISTENERS_URI, + false, this, UserHandle.USER_ALL); + update(null); + } + + @Override public void onChange(boolean selfChange, Uri uri) { + update(uri); + } + + public void update(Uri uri) { + ContentResolver resolver = getContext().getContentResolver(); + if (uri == null || NOTIFICATION_LIGHT_PULSE_URI.equals(uri)) { + boolean pulseEnabled = Settings.System.getInt(resolver, + Settings.System.NOTIFICATION_LIGHT_PULSE, 0) != 0; + if (mNotificationPulseEnabled != pulseEnabled) { + mNotificationPulseEnabled = pulseEnabled; + updateNotificationPulse(); + } + } + if (uri == null || ENABLED_NOTIFICATION_LISTENERS_URI.equals(uri)) { + rebindListenerServices(); + } + } + } + + private SettingsObserver mSettingsObserver; + + static long[] getLongArray(Resources r, int resid, int maxlen, long[] def) { + int[] ar = r.getIntArray(resid); + if (ar == null) { + return def; + } + final int len = ar.length > maxlen ? maxlen : ar.length; + long[] out = new long[len]; + for (int i=0; i scorerClass = getContext().getClassLoader().loadClass(scorerName); + NotificationScorer scorer = (NotificationScorer) scorerClass.newInstance(); + scorer.initialize(getContext()); + mScorers.add(scorer); + } catch (ClassNotFoundException e) { + Slog.w(TAG, "Couldn't find scorer " + scorerName + ".", e); + } catch (InstantiationException e) { + Slog.w(TAG, "Couldn't instantiate scorer " + scorerName + ".", e); + } catch (IllegalAccessException e) { + Slog.w(TAG, "Problem accessing scorer " + scorerName + ".", e); + } + } + + publishBinderService(Context.NOTIFICATION_SERVICE, mService); + publishLocalService(NotificationManagerInternal.class, mInternalService); + } + + /** + * Read the old XML-based app block database and import those blockages into the AppOps system. + */ + private void importOldBlockDb() { + loadBlockDb(); + + PackageManager pm = getContext().getPackageManager(); + for (String pkg : mBlockedPackages) { + PackageInfo info = null; + try { + info = pm.getPackageInfo(pkg, 0); + setNotificationsEnabledForPackageImpl(pkg, info.applicationInfo.uid, false); + } catch (NameNotFoundException e) { + // forget you + } + } + mBlockedPackages.clear(); + if (mPolicyFile != null) { + mPolicyFile.delete(); + } + } + + @Override + public void onBootPhase(int phase) { + if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { + // no beeping until we're basically done booting + mSystemReady = true; + + // Grab our optional AudioService + mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); + + // make sure our listener services are properly bound + rebindListenerServices(); + } + } + + void setNotificationsEnabledForPackageImpl(String pkg, int uid, boolean enabled) { + Slog.v(TAG, (enabled?"en":"dis") + "abling notifications for " + pkg); + + mAppOps.setMode(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg, + enabled ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED); + + // Now, cancel any outstanding notifications that are part of a just-disabled app + if (ENABLE_BLOCKED_NOTIFICATIONS && !enabled) { + cancelAllNotificationsInt(pkg, 0, 0, true, UserHandle.getUserId(uid)); + } + } + + private final IBinder mService = new INotificationManager.Stub() { + // Toasts + // ============================================================================ + + @Override + public void enqueueToast(String pkg, ITransientNotification callback, int duration) + { + if (DBG) { + Slog.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback + + " duration=" + duration); + } + + if (pkg == null || callback == null) { + Slog.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback); + return ; + } + + final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg)); + + if (ENABLE_BLOCKED_TOASTS && !noteNotificationOp(pkg, Binder.getCallingUid())) { + if (!isSystemToast) { + Slog.e(TAG, "Suppressing toast from package " + pkg + " by user request."); + return; + } + } + + synchronized (mToastQueue) { + int callingPid = Binder.getCallingPid(); + long callingId = Binder.clearCallingIdentity(); + try { + ToastRecord record; + int index = indexOfToastLocked(pkg, callback); + // If it's already in the queue, we update it in place, we don't + // move it to the end of the queue. + if (index >= 0) { + record = mToastQueue.get(index); + record.update(duration); + } else { + // Limit the number of toasts that any given package except the android + // package can enqueue. Prevents DOS attacks and deals with leaks. + if (!isSystemToast) { + int count = 0; + final int N = mToastQueue.size(); + for (int i=0; i= MAX_PACKAGE_NOTIFICATIONS) { + Slog.e(TAG, "Package has already posted " + count + + " toasts. Not showing more. Package=" + pkg); + return; + } + } + } + } + + record = new ToastRecord(callingPid, pkg, callback, duration); + mToastQueue.add(record); + index = mToastQueue.size() - 1; + keepProcessAliveLocked(callingPid); + } + // If it's at index 0, it's the current toast. It doesn't matter if it's + // new or just been updated. Call back and tell it to show itself. + // If the callback fails, this will remove it from the list, so don't + // assume that it's valid after this. + if (index == 0) { + showNextToastLocked(); + } + } finally { + Binder.restoreCallingIdentity(callingId); + } + } + } + + @Override + public void cancelToast(String pkg, ITransientNotification callback) { + Slog.i(TAG, "cancelToast pkg=" + pkg + " callback=" + callback); + + if (pkg == null || callback == null) { + Slog.e(TAG, "Not cancelling notification. pkg=" + pkg + " callback=" + callback); + return ; + } + + synchronized (mToastQueue) { + long callingId = Binder.clearCallingIdentity(); + try { + int index = indexOfToastLocked(pkg, callback); + if (index >= 0) { + cancelToastLocked(index); + } else { + Slog.w(TAG, "Toast already cancelled. pkg=" + pkg + + " callback=" + callback); + } + } finally { + Binder.restoreCallingIdentity(callingId); + } + } + } + + @Override + public void enqueueNotificationWithTag(String pkg, String basePkg, String tag, int id, + Notification notification, int[] idOut, int userId) throws RemoteException { + enqueueNotificationInternal(pkg, basePkg, Binder.getCallingUid(), + Binder.getCallingPid(), tag, id, notification, idOut, userId); + } + + @Override + public void cancelNotificationWithTag(String pkg, String tag, int id, int userId) { + checkCallerIsSystemOrSameApp(pkg); + userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), + Binder.getCallingUid(), userId, true, false, "cancelNotificationWithTag", pkg); + // Don't allow client applications to cancel foreground service notis. + cancelNotification(pkg, tag, id, 0, + Binder.getCallingUid() == Process.SYSTEM_UID + ? 0 : Notification.FLAG_FOREGROUND_SERVICE, false, userId); + } + + @Override + public void cancelAllNotifications(String pkg, int userId) { + checkCallerIsSystemOrSameApp(pkg); + + userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), + Binder.getCallingUid(), userId, true, false, "cancelAllNotifications", pkg); + + // Calling from user space, don't allow the canceling of actively + // running foreground services. + cancelAllNotificationsInt(pkg, 0, Notification.FLAG_FOREGROUND_SERVICE, true, userId); + } + + @Override + public void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled) { + checkCallerIsSystem(); + + setNotificationsEnabledForPackageImpl(pkg, uid, enabled); + } + + /** + * Use this when you just want to know if notifications are OK for this package. + */ + @Override + public boolean areNotificationsEnabledForPackage(String pkg, int uid) { + checkCallerIsSystem(); + return (mAppOps.checkOpNoThrow(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg) + == AppOpsManager.MODE_ALLOWED); + } + + /** + * System-only API for getting a list of current (i.e. not cleared) notifications. + * + * Requires ACCESS_NOTIFICATIONS which is signature|system. + */ + @Override + public StatusBarNotification[] getActiveNotifications(String callingPkg) { + // enforce() will ensure the calling uid has the correct permission + getContext().enforceCallingOrSelfPermission( + android.Manifest.permission.ACCESS_NOTIFICATIONS, + "NotificationManagerService.getActiveNotifications"); + + StatusBarNotification[] tmp = null; + int uid = Binder.getCallingUid(); + + // noteOp will check to make sure the callingPkg matches the uid + if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg) + == AppOpsManager.MODE_ALLOWED) { + synchronized (mNotificationList) { + tmp = new StatusBarNotification[mNotificationList.size()]; + final int N = mNotificationList.size(); + for (int i=0; i list = new ArrayList(); + synchronized (mNotificationList) { + final int N = mNotificationList.size(); + for (int i=0; i 0) { + pw.println(" Toast Queue:"); + for (int i=0; i 0) { + pw.println(" Notification List:"); + for (int i=0; i 0) { + pw.println(" Lights List:"); + for (int i=0; i= 5) { + if (iter.hasNext()) pw.println(" ..."); + break; + } + } + + } + } + + /** + * The private API only accessible to the system process. + */ + private final NotificationManagerInternal mInternalService = new NotificationManagerInternal() { + @Override + public void enqueueNotification(String pkg, String basePkg, int callingUid, int callingPid, + String tag, int id, Notification notification, int[] idReceived, int userId) { + enqueueNotificationInternal(pkg, basePkg, callingUid, callingPid, tag, id, notification, + idReceived, userId); + } + }; + + void enqueueNotificationInternal(final String pkg, String basePkg, final int callingUid, + final int callingPid, final String tag, final int id, final Notification notification, + int[] idOut, int incomingUserId) { + if (DBG) { + Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id + + " notification=" + notification); + } + checkCallerIsSystemOrSameApp(pkg); + final boolean isSystemNotification = isUidSystem(callingUid) || ("android".equals(pkg)); + + final int userId = ActivityManager.handleIncomingUser(callingPid, + callingUid, incomingUserId, true, false, "enqueueNotification", pkg); + final UserHandle user = new UserHandle(userId); + + // Limit the number of notifications that any given package except the android + // package can enqueue. Prevents DOS attacks and deals with leaks. + if (!isSystemNotification) { + synchronized (mNotificationList) { + int count = 0; + final int N = mNotificationList.size(); + for (int i=0; i= MAX_PACKAGE_NOTIFICATIONS) { + Slog.e(TAG, "Package has already posted " + count + + " notifications. Not showing more. package=" + pkg); + return; + } + } + } + } + } + + // This conditional is a dirty hack to limit the logging done on + // behalf of the download manager without affecting other apps. + if (!pkg.equals("com.android.providers.downloads") + || Log.isLoggable("DownloadManager", Log.VERBOSE)) { + EventLog.writeEvent(EventLogTags.NOTIFICATION_ENQUEUE, pkg, id, tag, userId, + notification.toString()); + } + + if (pkg == null || notification == null) { + throw new IllegalArgumentException("null not allowed: pkg=" + pkg + + " id=" + id + " notification=" + notification); + } + if (notification.icon != 0) { + if (notification.contentView == null) { + throw new IllegalArgumentException("contentView required: pkg=" + pkg + + " id=" + id + " notification=" + notification); + } + } + + mHandler.post(new Runnable() { + @Override + public void run() { + + // === Scoring === + + // 0. Sanitize inputs + notification.priority = clamp(notification.priority, Notification.PRIORITY_MIN, + Notification.PRIORITY_MAX); + // Migrate notification flags to scores + if (0 != (notification.flags & Notification.FLAG_HIGH_PRIORITY)) { + if (notification.priority < Notification.PRIORITY_MAX) { + notification.priority = Notification.PRIORITY_MAX; + } + } else if (SCORE_ONGOING_HIGHER && + 0 != (notification.flags & Notification.FLAG_ONGOING_EVENT)) { + if (notification.priority < Notification.PRIORITY_HIGH) { + notification.priority = Notification.PRIORITY_HIGH; + } + } + + // 1. initial score: buckets of 10, around the app + int score = notification.priority * NOTIFICATION_PRIORITY_MULTIPLIER; //[-20..20] + + // 2. Consult external heuristics (TBD) + + // 3. Apply local rules + + int initialScore = score; + if (!mScorers.isEmpty()) { + if (DBG) Slog.v(TAG, "Initial score is " + score + "."); + for (NotificationScorer scorer : mScorers) { + try { + score = scorer.getScore(notification, score); + } catch (Throwable t) { + Slog.w(TAG, "Scorer threw on .getScore.", t); + } + } + if (DBG) Slog.v(TAG, "Final score is " + score + "."); + } + + // add extra to indicate score modified by NotificationScorer + notification.extras.putBoolean(Notification.EXTRA_SCORE_MODIFIED, + score != initialScore); + + // blocked apps + if (ENABLE_BLOCKED_NOTIFICATIONS && !noteNotificationOp(pkg, callingUid)) { + if (!isSystemNotification) { + score = JUNK_SCORE; + Slog.e(TAG, "Suppressing notification from package " + pkg + + " by user request."); + } + } + + if (DBG) { + Slog.v(TAG, "Assigned score=" + score + " to " + notification); + } + + if (score < SCORE_DISPLAY_THRESHOLD) { + // Notification will be blocked because the score is too low. + return; + } + + // Should this notification make noise, vibe, or use the LED? + final boolean canInterrupt = (score >= SCORE_INTERRUPTION_THRESHOLD); + + synchronized (mNotificationList) { + final StatusBarNotification n = new StatusBarNotification( + pkg, id, tag, callingUid, callingPid, score, notification, user); + NotificationRecord r = new NotificationRecord(n); + NotificationRecord old = null; + + int index = indexOfNotificationLocked(pkg, tag, id, userId); + if (index < 0) { + mNotificationList.add(r); + } else { + old = mNotificationList.remove(index); + mNotificationList.add(index, r); + // Make sure we don't lose the foreground service state. + if (old != null) { + notification.flags |= + old.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE; + } + } + + // Ensure if this is a foreground service that the proper additional + // flags are set. + if ((notification.flags&Notification.FLAG_FOREGROUND_SERVICE) != 0) { + notification.flags |= Notification.FLAG_ONGOING_EVENT + | Notification.FLAG_NO_CLEAR; + } + + final int currentUser; + final long token = Binder.clearCallingIdentity(); + try { + currentUser = ActivityManager.getCurrentUser(); + } finally { + Binder.restoreCallingIdentity(token); + } + + if (notification.icon != 0) { + if (old != null && old.statusBarKey != null) { + r.statusBarKey = old.statusBarKey; + final long identity = Binder.clearCallingIdentity(); + try { + mStatusBar.updateNotification(r.statusBarKey, n); + } finally { + Binder.restoreCallingIdentity(identity); + } + } else { + final long identity = Binder.clearCallingIdentity(); + try { + r.statusBarKey = mStatusBar.addNotification(n); + if ((n.getNotification().flags & Notification.FLAG_SHOW_LIGHTS) != 0 + && canInterrupt) { + mAttentionLight.pulse(); + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + // Send accessibility events only for the current user. + if (currentUser == userId) { + sendAccessibilityEvent(notification, pkg); + } + + notifyPostedLocked(r); + } else { + Slog.e(TAG, "Not posting notification with icon==0: " + notification); + if (old != null && old.statusBarKey != null) { + final long identity = Binder.clearCallingIdentity(); + try { + mStatusBar.removeNotification(old.statusBarKey); + } finally { + Binder.restoreCallingIdentity(identity); + } + + notifyRemovedLocked(r); + } + // ATTENTION: in a future release we will bail out here + // so that we do not play sounds, show lights, etc. for invalid + // notifications + Slog.e(TAG, "WARNING: In a future release this will crash the app: " + + n.getPackageName()); + } + + // If we're not supposed to beep, vibrate, etc. then don't. + if (((mDisabledNotifications + & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) == 0) + && (!(old != null + && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 )) + && (r.getUserId() == UserHandle.USER_ALL || + (r.getUserId() == userId && r.getUserId() == currentUser)) + && canInterrupt + && mSystemReady + && mAudioManager != null) { + + // sound + + // should we use the default notification sound? (indicated either by + // DEFAULT_SOUND or because notification.sound is pointing at + // Settings.System.NOTIFICATION_SOUND) + final boolean useDefaultSound = + (notification.defaults & Notification.DEFAULT_SOUND) != 0 || + Settings.System.DEFAULT_NOTIFICATION_URI + .equals(notification.sound); + + Uri soundUri = null; + boolean hasValidSound = false; + + if (useDefaultSound) { + soundUri = Settings.System.DEFAULT_NOTIFICATION_URI; + + // check to see if the default notification sound is silent + ContentResolver resolver = getContext().getContentResolver(); + hasValidSound = Settings.System.getString(resolver, + Settings.System.NOTIFICATION_SOUND) != null; + } else if (notification.sound != null) { + soundUri = notification.sound; + hasValidSound = (soundUri != null); + } + + if (hasValidSound) { + boolean looping = + (notification.flags & Notification.FLAG_INSISTENT) != 0; + int audioStreamType; + if (notification.audioStreamType >= 0) { + audioStreamType = notification.audioStreamType; + } else { + audioStreamType = DEFAULT_STREAM_TYPE; + } + mSoundNotification = r; + // do not play notifications if stream volume is 0 (typically because + // ringer mode is silent) or if there is a user of exclusive audio focus + if ((mAudioManager.getStreamVolume(audioStreamType) != 0) + && !mAudioManager.isAudioFocusExclusive()) { + final long identity = Binder.clearCallingIdentity(); + try { + final IRingtonePlayer player = + mAudioManager.getRingtonePlayer(); + if (player != null) { + player.playAsync(soundUri, user, looping, audioStreamType); + } + } catch (RemoteException e) { + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + + // vibrate + // Does the notification want to specify its own vibration? + final boolean hasCustomVibrate = notification.vibrate != null; + + // new in 4.2: if there was supposed to be a sound and we're in vibrate + // mode, and no other vibration is specified, we fall back to vibration + final boolean convertSoundToVibration = + !hasCustomVibrate + && hasValidSound + && (mAudioManager.getRingerMode() + == AudioManager.RINGER_MODE_VIBRATE); + + // The DEFAULT_VIBRATE flag trumps any custom vibration AND the fallback. + final boolean useDefaultVibrate = + (notification.defaults & Notification.DEFAULT_VIBRATE) != 0; + + if ((useDefaultVibrate || convertSoundToVibration || hasCustomVibrate) + && !(mAudioManager.getRingerMode() + == AudioManager.RINGER_MODE_SILENT)) { + mVibrateNotification = r; + + if (useDefaultVibrate || convertSoundToVibration) { + // Escalate privileges so we can use the vibrator even if the + // notifying app does not have the VIBRATE permission. + long identity = Binder.clearCallingIdentity(); + try { + mVibrator.vibrate(r.sbn.getUid(), r.sbn.getBasePkg(), + useDefaultVibrate ? mDefaultVibrationPattern + : mFallbackVibrationPattern, + ((notification.flags & Notification.FLAG_INSISTENT) != 0) + ? 0: -1); + } finally { + Binder.restoreCallingIdentity(identity); + } + } else if (notification.vibrate.length > 1) { + // If you want your own vibration pattern, you need the VIBRATE + // permission + mVibrator.vibrate(r.sbn.getUid(), r.sbn.getBasePkg(), + notification.vibrate, + ((notification.flags & Notification.FLAG_INSISTENT) != 0) + ? 0: -1); + } + } + } + + // light + // the most recent thing gets the light + mLights.remove(old); + if (mLedNotification == old) { + mLedNotification = null; + } + //Slog.i(TAG, "notification.lights=" + // + ((old.notification.lights.flags & Notification.FLAG_SHOW_LIGHTS) + // != 0)); + if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0 + && canInterrupt) { + mLights.add(r); + updateLightsLocked(); + } else { + if (old != null + && ((old.getFlags() & Notification.FLAG_SHOW_LIGHTS) != 0)) { + updateLightsLocked(); + } + } + } + } + }); + + idOut[0] = id; + } + + void registerListenerImpl(final INotificationListener listener, + final ComponentName component, final int userid) { + synchronized (mNotificationList) { + try { + NotificationListenerInfo info + = new NotificationListenerInfo(listener, component, userid, true); + listener.asBinder().linkToDeath(info, 0); + mListeners.add(info); + } catch (RemoteException e) { + // already dead + } + } + } + + void unregisterListenerImpl(final INotificationListener listener, final int userid) { + synchronized (mNotificationList) { + final int N = mListeners.size(); + for (int i=N-1; i>=0; i--) { + final NotificationListenerInfo info = mListeners.get(i); + if (info.listener.asBinder() == listener.asBinder() + && info.userid == userid) { + mListeners.remove(i); + if (info.connection != null) { + getContext().unbindService(info.connection); + } + } + } + } + } + + void showNextToastLocked() { + ToastRecord record = mToastQueue.get(0); + while (record != null) { + if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback); + try { + record.callback.show(); + scheduleTimeoutLocked(record); + return; + } catch (RemoteException e) { + Slog.w(TAG, "Object died trying to show notification " + record.callback + + " in package " + record.pkg); + // remove it from the list and let the process die + int index = mToastQueue.indexOf(record); + if (index >= 0) { + mToastQueue.remove(index); + } + keepProcessAliveLocked(record.pid); + if (mToastQueue.size() > 0) { + record = mToastQueue.get(0); + } else { + record = null; + } + } + } + } + + void cancelToastLocked(int index) { + ToastRecord record = mToastQueue.get(index); + try { + record.callback.hide(); + } catch (RemoteException e) { + Slog.w(TAG, "Object died trying to hide notification " + record.callback + + " in package " + record.pkg); + // don't worry about this, we're about to remove it from + // the list anyway + } + mToastQueue.remove(index); + keepProcessAliveLocked(record.pid); + if (mToastQueue.size() > 0) { + // Show the next one. If the callback fails, this will remove + // it from the list, so don't assume that the list hasn't changed + // after this point. + showNextToastLocked(); + } + } + + private void scheduleTimeoutLocked(ToastRecord r) + { + mHandler.removeCallbacksAndMessages(r); + Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r); + long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY; + mHandler.sendMessageDelayed(m, delay); + } + + private void handleTimeout(ToastRecord record) + { + if (DBG) Slog.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback); + synchronized (mToastQueue) { + int index = indexOfToastLocked(record.pkg, record.callback); + if (index >= 0) { + cancelToastLocked(index); + } + } + } + + // lock on mToastQueue + int indexOfToastLocked(String pkg, ITransientNotification callback) + { + IBinder cbak = callback.asBinder(); + ArrayList list = mToastQueue; + int len = list.size(); + for (int i=0; i list = mToastQueue; + int N = list.size(); + for (int i=0; i 0); + } catch (RemoteException e) { + // Shouldn't happen. + } + } + + private final class WorkerHandler extends Handler + { + @Override + public void handleMessage(Message msg) + { + switch (msg.what) + { + case MESSAGE_TIMEOUT: + handleTimeout((ToastRecord)msg.obj); + break; + } + } + } + + + // Notifications + // ============================================================================ + static int clamp(int x, int low, int high) { + return (x < low) ? low : ((x > high) ? high : x); + } + + void sendAccessibilityEvent(Notification notification, CharSequence packageName) { + AccessibilityManager manager = AccessibilityManager.getInstance(getContext()); + if (!manager.isEnabled()) { + return; + } + + AccessibilityEvent event = + AccessibilityEvent.obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED); + event.setPackageName(packageName); + event.setClassName(Notification.class.getName()); + event.setParcelableData(notification); + CharSequence tickerText = notification.tickerText; + if (!TextUtils.isEmpty(tickerText)) { + event.getText().add(tickerText); + } + + manager.sendAccessibilityEvent(event); + } + + private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete) { + // tell the app + if (sendDelete) { + if (r.getNotification().deleteIntent != null) { + try { + r.getNotification().deleteIntent.send(); + } catch (PendingIntent.CanceledException ex) { + // do nothing - there's no relevant way to recover, and + // no reason to let this propagate + Slog.w(TAG, "canceled PendingIntent for " + r.sbn.getPackageName(), ex); + } + } + } + + // status bar + if (r.getNotification().icon != 0) { + final long identity = Binder.clearCallingIdentity(); + try { + mStatusBar.removeNotification(r.statusBarKey); + } finally { + Binder.restoreCallingIdentity(identity); + } + r.statusBarKey = null; + notifyRemovedLocked(r); + } + + // sound + if (mSoundNotification == r) { + mSoundNotification = null; + final long identity = Binder.clearCallingIdentity(); + try { + final IRingtonePlayer player = mAudioManager.getRingtonePlayer(); + if (player != null) { + player.stopAsync(); + } + } catch (RemoteException e) { + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + // vibrate + if (mVibrateNotification == r) { + mVibrateNotification = null; + long identity = Binder.clearCallingIdentity(); + try { + mVibrator.cancel(); + } + finally { + Binder.restoreCallingIdentity(identity); + } + } + + // light + mLights.remove(r); + if (mLedNotification == r) { + mLedNotification = null; + } + + // Save it for users of getHistoricalNotifications() + mArchive.record(r.sbn); + } + + /** + * Cancels a notification ONLY if it has all of the {@code mustHaveFlags} + * and none of the {@code mustNotHaveFlags}. + */ + void cancelNotification(final String pkg, final String tag, final int id, + final int mustHaveFlags, final int mustNotHaveFlags, final boolean sendDelete, + final int userId) { + // In enqueueNotificationInternal notifications are added by scheduling the + // work on the worker handler. Hence, we also schedule the cancel on this + // handler to avoid a scenario where an add notification call followed by a + // remove notification call ends up in not removing the notification. + mHandler.post(new Runnable() { + @Override + public void run() { + EventLog.writeEvent(EventLogTags.NOTIFICATION_CANCEL, pkg, id, tag, userId, + mustHaveFlags, mustNotHaveFlags); + + synchronized (mNotificationList) { + int index = indexOfNotificationLocked(pkg, tag, id, userId); + if (index >= 0) { + NotificationRecord r = mNotificationList.get(index); + + if ((r.getNotification().flags & mustHaveFlags) != mustHaveFlags) { + return; + } + if ((r.getNotification().flags & mustNotHaveFlags) != 0) { + return; + } + + mNotificationList.remove(index); + + cancelNotificationLocked(r, sendDelete); + updateLightsLocked(); + } + } + } + }); + } + + /** + * Determine whether the userId applies to the notification in question, either because + * they match exactly, or one of them is USER_ALL (which is treated as a wildcard). + */ + private boolean notificationMatchesUserId(NotificationRecord r, int userId) { + return + // looking for USER_ALL notifications? match everything + userId == UserHandle.USER_ALL + // a notification sent to USER_ALL matches any query + || r.getUserId() == UserHandle.USER_ALL + // an exact user match + || r.getUserId() == userId; + } + + /** + * Cancels all notifications from a given package that have all of the + * {@code mustHaveFlags}. + */ + boolean cancelAllNotificationsInt(String pkg, int mustHaveFlags, + int mustNotHaveFlags, boolean doit, int userId) { + EventLog.writeEvent(EventLogTags.NOTIFICATION_CANCEL_ALL, pkg, userId, + mustHaveFlags, mustNotHaveFlags); + + synchronized (mNotificationList) { + final int N = mNotificationList.size(); + boolean canceledSomething = false; + for (int i = N-1; i >= 0; --i) { + NotificationRecord r = mNotificationList.get(i); + if (!notificationMatchesUserId(r, userId)) { + continue; + } + // Don't remove notifications to all, if there's no package name specified + if (r.getUserId() == UserHandle.USER_ALL && pkg == null) { + continue; + } + if ((r.getFlags() & mustHaveFlags) != mustHaveFlags) { + continue; + } + if ((r.getFlags() & mustNotHaveFlags) != 0) { + continue; + } + if (pkg != null && !r.sbn.getPackageName().equals(pkg)) { + continue; + } + canceledSomething = true; + if (!doit) { + return true; + } + mNotificationList.remove(i); + cancelNotificationLocked(r, false); + } + if (canceledSomething) { + updateLightsLocked(); + } + return canceledSomething; + } + } + + + + // Return true if the UID is a system or phone UID and therefore should not have + // any notifications or toasts blocked. + boolean isUidSystem(int uid) { + final int appid = UserHandle.getAppId(uid); + return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0); + } + + // same as isUidSystem(int, int) for the Binder caller's UID. + boolean isCallerSystem() { + return isUidSystem(Binder.getCallingUid()); + } + + void checkCallerIsSystem() { + if (isCallerSystem()) { + return; + } + throw new SecurityException("Disallowed call for uid " + Binder.getCallingUid()); + } + + void checkCallerIsSystemOrSameApp(String pkg) { + if (isCallerSystem()) { + return; + } + final int uid = Binder.getCallingUid(); + try { + ApplicationInfo ai = AppGlobals.getPackageManager().getApplicationInfo( + pkg, 0, UserHandle.getCallingUserId()); + if (!UserHandle.isSameApp(ai.uid, uid)) { + throw new SecurityException("Calling uid " + uid + " gave package" + + pkg + " which is owned by uid " + ai.uid); + } + } catch (RemoteException re) { + throw new SecurityException("Unknown package " + pkg + "\n" + re); + } + } + + void cancelAll(int userId) { + synchronized (mNotificationList) { + final int N = mNotificationList.size(); + for (int i=N-1; i>=0; i--) { + NotificationRecord r = mNotificationList.get(i); + + if (!notificationMatchesUserId(r, userId)) { + continue; + } + + if ((r.getFlags() & (Notification.FLAG_ONGOING_EVENT + | Notification.FLAG_NO_CLEAR)) == 0) { + mNotificationList.remove(i); + cancelNotificationLocked(r, true); + } + } + + updateLightsLocked(); + } + } + + // lock on mNotificationList + void updateLightsLocked() + { + // handle notification lights + if (mLedNotification == null) { + // get next notification, if any + int n = mLights.size(); + if (n > 0) { + mLedNotification = mLights.get(n-1); + } + } + + // Don't flash while we are in a call or screen is on + if (mLedNotification == null || mInCall || mScreenOn) { + mNotificationLight.turnOff(); + } else { + final Notification ledno = mLedNotification.sbn.getNotification(); + int ledARGB = ledno.ledARGB; + int ledOnMS = ledno.ledOnMS; + int ledOffMS = ledno.ledOffMS; + if ((ledno.defaults & Notification.DEFAULT_LIGHTS) != 0) { + ledARGB = mDefaultNotificationColor; + ledOnMS = mDefaultNotificationLedOn; + ledOffMS = mDefaultNotificationLedOff; + } + if (mNotificationPulseEnabled) { + // pulse repeatedly + mNotificationLight.setFlashing(ledARGB, Light.LIGHT_FLASH_TIMED, + ledOnMS, ledOffMS); + } + } + } + + // lock on mNotificationList + int indexOfNotificationLocked(String pkg, String tag, int id, int userId) + { + ArrayList list = mNotificationList; + final int len = list.size(); + for (int i=0; i mNotifications + = new HashMap(); + + // for disabling the status bar + private final ArrayList mDisableRecords = new ArrayList(); + private IBinder mSysUiVisToken = new Binder(); + private int mDisabled = 0; + + private Object mLock = new Object(); + // encompasses lights-out mode and other flags defined on View + private int mSystemUiVisibility = 0; + private boolean mMenuVisible = false; + private int mImeWindowVis = 0; + private int mImeBackDisposition; + private IBinder mImeToken = null; + private int mCurrentUserId; + + private class DisableRecord implements IBinder.DeathRecipient { + int userId; + String pkg; + int what; + IBinder token; + + public void binderDied() { + Slog.i(TAG, "binder died for pkg=" + pkg); + disableInternal(userId, 0, token, pkg); + token.unlinkToDeath(this, 0); + } + } + + /** + * Construct the service, add the status bar view to the window manager + */ + public StatusBarManagerService(Context context, WindowManagerService windowManager) { + mContext = context; + mWindowManager = windowManager; + mWindowManager.setOnHardKeyboardStatusChangeListener(this); + + final Resources res = context.getResources(); + mIcons.defineSlots(res.getStringArray(com.android.internal.R.array.config_statusBarIcons)); + + LocalServices.addService(StatusBarManagerInternal.class, mInternalService); + } + + /** + * Private API used by NotificationManagerService. + */ + private final StatusBarManagerInternal mInternalService = new StatusBarManagerInternal() { + @Override + public void setNotificationDelegate(NotificationDelegate delegate) { + synchronized (mNotifications) { + mNotificationDelegate = delegate; + } + } + + @Override + public IBinder addNotification(StatusBarNotification notification) { + synchronized (mNotifications) { + IBinder key = new Binder(); + mNotifications.put(key, notification); + if (mBar != null) { + try { + mBar.addNotification(key, notification); + } catch (RemoteException ex) { + } + } + return key; + } + } + + @Override + public void updateNotification(IBinder key, StatusBarNotification notification) { + synchronized (mNotifications) { + if (!mNotifications.containsKey(key)) { + throw new IllegalArgumentException("updateNotification key not found: " + key); + } + mNotifications.put(key, notification); + if (mBar != null) { + try { + mBar.updateNotification(key, notification); + } catch (RemoteException ex) { + } + } + } + } + + @Override + public void removeNotification(IBinder key) { + synchronized (mNotifications) { + final StatusBarNotification n = mNotifications.remove(key); + if (n == null) { + Slog.e(TAG, "removeNotification key not found: " + key); + return; + } + if (mBar != null) { + try { + mBar.removeNotification(key); + } catch (RemoteException ex) { + } + } + } + } + }; + + // ================================================================================ + // From IStatusBarService + // ================================================================================ + @Override + public void expandNotificationsPanel() { + enforceExpandStatusBar(); + + if (mBar != null) { + try { + mBar.animateExpandNotificationsPanel(); + } catch (RemoteException ex) { + } + } + } + + @Override + public void collapsePanels() { + enforceExpandStatusBar(); + + if (mBar != null) { + try { + mBar.animateCollapsePanels(); + } catch (RemoteException ex) { + } + } + } + + @Override + public void expandSettingsPanel() { + enforceExpandStatusBar(); + + if (mBar != null) { + try { + mBar.animateExpandSettingsPanel(); + } catch (RemoteException ex) { + } + } + } + + @Override + public void disable(int what, IBinder token, String pkg) { + disableInternal(mCurrentUserId, what, token, pkg); + } + + private void disableInternal(int userId, int what, IBinder token, String pkg) { + enforceStatusBar(); + + synchronized (mLock) { + disableLocked(userId, what, token, pkg); + } + } + + private void disableLocked(int userId, int what, IBinder token, String pkg) { + // It's important that the the callback and the call to mBar get done + // in the same order when multiple threads are calling this function + // so they are paired correctly. The messages on the handler will be + // handled in the order they were enqueued, but will be outside the lock. + manageDisableListLocked(userId, what, token, pkg); + + // Ensure state for the current user is applied, even if passed a non-current user. + final int net = gatherDisableActionsLocked(mCurrentUserId); + if (net != mDisabled) { + mDisabled = net; + mHandler.post(new Runnable() { + public void run() { + mNotificationDelegate.onSetDisabled(net); + } + }); + if (mBar != null) { + try { + mBar.disable(net); + } catch (RemoteException ex) { + } + } + } + } + + @Override + public void setIcon(String slot, String iconPackage, int iconId, int iconLevel, + String contentDescription) { + enforceStatusBar(); + + synchronized (mIcons) { + int index = mIcons.getSlotIndex(slot); + if (index < 0) { + throw new SecurityException("invalid status bar icon slot: " + slot); + } + + StatusBarIcon icon = new StatusBarIcon(iconPackage, UserHandle.OWNER, iconId, + iconLevel, 0, + contentDescription); + //Slog.d(TAG, "setIcon slot=" + slot + " index=" + index + " icon=" + icon); + mIcons.setIcon(index, icon); + + if (mBar != null) { + try { + mBar.setIcon(index, icon); + } catch (RemoteException ex) { + } + } + } + } + + @Override + public void setIconVisibility(String slot, boolean visible) { + enforceStatusBar(); + + synchronized (mIcons) { + int index = mIcons.getSlotIndex(slot); + if (index < 0) { + throw new SecurityException("invalid status bar icon slot: " + slot); + } + + StatusBarIcon icon = mIcons.getIcon(index); + if (icon == null) { + return; + } + + if (icon.visible != visible) { + icon.visible = visible; + + if (mBar != null) { + try { + mBar.setIcon(index, icon); + } catch (RemoteException ex) { + } + } + } + } + } + + @Override + public void removeIcon(String slot) { + enforceStatusBar(); + + synchronized (mIcons) { + int index = mIcons.getSlotIndex(slot); + if (index < 0) { + throw new SecurityException("invalid status bar icon slot: " + slot); + } + + mIcons.removeIcon(index); + + if (mBar != null) { + try { + mBar.removeIcon(index); + } catch (RemoteException ex) { + } + } + } + } + + /** + * Hide or show the on-screen Menu key. Only call this from the window manager, typically in + * response to a window with FLAG_NEEDS_MENU_KEY set. + */ + @Override + public void topAppWindowChanged(final boolean menuVisible) { + enforceStatusBar(); + + if (SPEW) Slog.d(TAG, (menuVisible?"showing":"hiding") + " MENU key"); + + synchronized(mLock) { + mMenuVisible = menuVisible; + mHandler.post(new Runnable() { + public void run() { + if (mBar != null) { + try { + mBar.topAppWindowChanged(menuVisible); + } catch (RemoteException ex) { + } + } + } + }); + } + } + + @Override + public void setImeWindowStatus(final IBinder token, final int vis, final int backDisposition) { + enforceStatusBar(); + + if (SPEW) { + Slog.d(TAG, "swetImeWindowStatus vis=" + vis + " backDisposition=" + backDisposition); + } + + synchronized(mLock) { + // In case of IME change, we need to call up setImeWindowStatus() regardless of + // mImeWindowVis because mImeWindowVis may not have been set to false when the + // previous IME was destroyed. + mImeWindowVis = vis; + mImeBackDisposition = backDisposition; + mImeToken = token; + mHandler.post(new Runnable() { + public void run() { + if (mBar != null) { + try { + mBar.setImeWindowStatus(token, vis, backDisposition); + } catch (RemoteException ex) { + } + } + } + }); + } + } + + @Override + public void setSystemUiVisibility(int vis, int mask) { + // also allows calls from window manager which is in this process. + enforceStatusBarService(); + + if (SPEW) Slog.d(TAG, "setSystemUiVisibility(0x" + Integer.toHexString(vis) + ")"); + + synchronized (mLock) { + updateUiVisibilityLocked(vis, mask); + disableLocked( + mCurrentUserId, + vis & StatusBarManager.DISABLE_MASK, + mSysUiVisToken, + "WindowManager.LayoutParams"); + } + } + + private void updateUiVisibilityLocked(final int vis, final int mask) { + if (mSystemUiVisibility != vis) { + mSystemUiVisibility = vis; + mHandler.post(new Runnable() { + public void run() { + if (mBar != null) { + try { + mBar.setSystemUiVisibility(vis, mask); + } catch (RemoteException ex) { + } + } + } + }); + } + } + + @Override + public void setHardKeyboardEnabled(final boolean enabled) { + mHandler.post(new Runnable() { + public void run() { + mWindowManager.setHardKeyboardEnabled(enabled); + } + }); + } + + @Override + public void onHardKeyboardStatusChange(final boolean available, final boolean enabled) { + mHandler.post(new Runnable() { + public void run() { + if (mBar != null) { + try { + mBar.setHardKeyboardStatus(available, enabled); + } catch (RemoteException ex) { + } + } + } + }); + } + + @Override + public void toggleRecentApps() { + if (mBar != null) { + try { + mBar.toggleRecentApps(); + } catch (RemoteException ex) {} + } + } + + @Override + public void preloadRecentApps() { + if (mBar != null) { + try { + mBar.preloadRecentApps(); + } catch (RemoteException ex) {} + } + } + + @Override + public void cancelPreloadRecentApps() { + if (mBar != null) { + try { + mBar.cancelPreloadRecentApps(); + } catch (RemoteException ex) {} + } + } + + @Override + public void setCurrentUser(int newUserId) { + if (SPEW) Slog.d(TAG, "Setting current user to user " + newUserId); + mCurrentUserId = newUserId; + } + + @Override + public void setWindowState(int window, int state) { + if (mBar != null) { + try { + mBar.setWindowState(window, state); + } catch (RemoteException ex) {} + } + } + + private void enforceStatusBar() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR, + "StatusBarManagerService"); + } + + private void enforceExpandStatusBar() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.EXPAND_STATUS_BAR, + "StatusBarManagerService"); + } + + private void enforceStatusBarService() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE, + "StatusBarManagerService"); + } + + // ================================================================================ + // Callbacks from the status bar service. + // ================================================================================ + @Override + public void registerStatusBar(IStatusBar bar, StatusBarIconList iconList, + List notificationKeys, List notifications, + int switches[], List binders) { + enforceStatusBarService(); + + Slog.i(TAG, "registerStatusBar bar=" + bar); + mBar = bar; + synchronized (mIcons) { + iconList.copyFrom(mIcons); + } + synchronized (mNotifications) { + for (Map.Entry e: mNotifications.entrySet()) { + notificationKeys.add(e.getKey()); + notifications.add(e.getValue()); + } + } + synchronized (mLock) { + switches[0] = gatherDisableActionsLocked(mCurrentUserId); + switches[1] = mSystemUiVisibility; + switches[2] = mMenuVisible ? 1 : 0; + switches[3] = mImeWindowVis; + switches[4] = mImeBackDisposition; + binders.add(mImeToken); + } + switches[5] = mWindowManager.isHardKeyboardAvailable() ? 1 : 0; + switches[6] = mWindowManager.isHardKeyboardEnabled() ? 1 : 0; + } + + /** + * The status bar service should call this each time the user brings the panel from + * invisible to visible in order to clear the notification light. + */ + @Override + public void onPanelRevealed() { + enforceStatusBarService(); + long identity = Binder.clearCallingIdentity(); + try { + // tell the notification manager to turn off the lights. + mNotificationDelegate.onPanelRevealed(); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void onNotificationClick(String pkg, String tag, int id) { + enforceStatusBarService(); + long identity = Binder.clearCallingIdentity(); + try { + mNotificationDelegate.onNotificationClick(pkg, tag, id); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void onNotificationError(String pkg, String tag, int id, + int uid, int initialPid, String message) { + enforceStatusBarService(); + long identity = Binder.clearCallingIdentity(); + try { + // WARNING: this will call back into us to do the remove. Don't hold any locks. + mNotificationDelegate.onNotificationError(pkg, tag, id, uid, initialPid, message); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void onNotificationClear(String pkg, String tag, int id) { + enforceStatusBarService(); + long identity = Binder.clearCallingIdentity(); + try { + mNotificationDelegate.onNotificationClear(pkg, tag, id); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void onClearAllNotifications() { + enforceStatusBarService(); + long identity = Binder.clearCallingIdentity(); + try { + mNotificationDelegate.onClearAll(); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + + // ================================================================================ + // Can be called from any thread + // ================================================================================ + + // lock on mDisableRecords + void manageDisableListLocked(int userId, int what, IBinder token, String pkg) { + if (SPEW) { + Slog.d(TAG, "manageDisableList userId=" + userId + + " what=0x" + Integer.toHexString(what) + " pkg=" + pkg); + } + // update the list + final int N = mDisableRecords.size(); + DisableRecord tok = null; + int i; + for (i=0; i e: mNotifications.entrySet()) { + pw.printf(" %2d: %s\n", i, e.getValue().toString()); + i++; + } + } + + synchronized (mLock) { + pw.println(" mDisabled=0x" + Integer.toHexString(mDisabled)); + final int N = mDisableRecords.size(); + pw.println(" mDisableRecords.size=" + N); + for (int i=0; i= freeMemLogInterval) { + mLastReportedFreeMemTime = currTime; + long mFreeSystem = -1, mFreeCache = -1; + try { + mSystemFileStats.restat(SYSTEM_PATH.getAbsolutePath()); + mFreeSystem = (long) mSystemFileStats.getAvailableBlocks() * + mSystemFileStats.getBlockSize(); + } catch (IllegalArgumentException e) { + // ignore; report -1 + } + try { + mCacheFileStats.restat(CACHE_PATH.getAbsolutePath()); + mFreeCache = (long) mCacheFileStats.getAvailableBlocks() * + mCacheFileStats.getBlockSize(); + } catch (IllegalArgumentException e) { + // ignore; report -1 + } + EventLog.writeEvent(EventLogTags.FREE_STORAGE_LEFT, + mFreeMem, mFreeSystem, mFreeCache); + } + // Read the reporting threshold from secure settings + long threshold = Settings.Global.getLong(mResolver, + Settings.Global.DISK_FREE_CHANGE_REPORTING_THRESHOLD, + DEFAULT_DISK_FREE_CHANGE_REPORTING_THRESHOLD); + // If mFree changed significantly log the new value + long delta = mFreeMem - mLastReportedFreeMem; + if (delta > threshold || delta < -threshold) { + mLastReportedFreeMem = mFreeMem; + EventLog.writeEvent(EventLogTags.FREE_STORAGE_CHANGED, mFreeMem); + } + } + + private void clearCache() { + if (mClearCacheObserver == null) { + // Lazy instantiation + mClearCacheObserver = new CachePackageDataObserver(); + } + mClearingCache = true; + try { + if (localLOGV) Slog.i(TAG, "Clearing cache"); + IPackageManager.Stub.asInterface(ServiceManager.getService("package")). + freeStorageAndNotify(mMemCacheTrimToThreshold, mClearCacheObserver); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to get handle for PackageManger Exception: "+e); + mClearingCache = false; + mClearSucceeded = false; + } + } + + void checkMemory(boolean checkCache) { + //if the thread that was started to clear cache is still running do nothing till its + //finished clearing cache. Ideally this flag could be modified by clearCache + // and should be accessed via a lock but even if it does this test will fail now and + //hopefully the next time this flag will be set to the correct value. + if(mClearingCache) { + if(localLOGV) Slog.i(TAG, "Thread already running just skip"); + //make sure the thread is not hung for too long + long diffTime = System.currentTimeMillis() - mThreadStartTime; + if(diffTime > (10*60*1000)) { + Slog.w(TAG, "Thread that clears cache file seems to run for ever"); + } + } else { + restatDataDir(); + if (localLOGV) Slog.v(TAG, "freeMemory="+mFreeMem); + + //post intent to NotificationManager to display icon if necessary + if (mFreeMem < mMemLowThreshold) { + if (checkCache) { + // We are allowed to clear cache files at this point to + // try to get down below the limit, because this is not + // the initial call after a cache clear has been attempted. + // In this case we will try a cache clear if our free + // space has gone below the cache clear limit. + if (mFreeMem < mMemCacheStartTrimThreshold) { + // We only clear the cache if the free storage has changed + // a significant amount since the last time. + if ((mFreeMemAfterLastCacheClear-mFreeMem) + >= ((mMemLowThreshold-mMemCacheStartTrimThreshold)/4)) { + // See if clearing cache helps + // Note that clearing cache is asynchronous and so we do a + // memory check again once the cache has been cleared. + mThreadStartTime = System.currentTimeMillis(); + mClearSucceeded = false; + clearCache(); + } + } + } else { + // This is a call from after clearing the cache. Note + // the amount of free storage at this point. + mFreeMemAfterLastCacheClear = mFreeMem; + if (!mLowMemFlag) { + // We tried to clear the cache, but that didn't get us + // below the low storage limit. Tell the user. + Slog.i(TAG, "Running low on memory. Sending notification"); + sendNotification(); + mLowMemFlag = true; + } else { + if (localLOGV) Slog.v(TAG, "Running low on memory " + + "notification already sent. do nothing"); + } + } + } else { + mFreeMemAfterLastCacheClear = mFreeMem; + if (mLowMemFlag) { + Slog.i(TAG, "Memory available. Cancelling notification"); + cancelNotification(); + mLowMemFlag = false; + } + } + if (mFreeMem < mMemFullThreshold) { + if (!mMemFullFlag) { + sendFullNotification(); + mMemFullFlag = true; + } + } else { + if (mMemFullFlag) { + cancelFullNotification(); + mMemFullFlag = false; + } + } + } + if(localLOGV) Slog.i(TAG, "Posting Message again"); + //keep posting messages to itself periodically + postCheckMemoryMsg(true, DEFAULT_CHECK_INTERVAL); + } + + void postCheckMemoryMsg(boolean clearCache, long delay) { + // Remove queued messages + mHandler.removeMessages(DEVICE_MEMORY_WHAT); + mHandler.sendMessageDelayed(mHandler.obtainMessage(DEVICE_MEMORY_WHAT, + clearCache ?_TRUE : _FALSE, 0), + delay); + } + + /** + * Constructor to run service. initializes the disk space threshold value + * and posts an empty message to kickstart the process. + */ + @Override + public void onCreate(Context context) { + mLastReportedFreeMemTime = 0; + mResolver = context.getContentResolver(); + //create StatFs object + mDataFileStats = new StatFs(DATA_PATH.getAbsolutePath()); + mSystemFileStats = new StatFs(SYSTEM_PATH.getAbsolutePath()); + mCacheFileStats = new StatFs(CACHE_PATH.getAbsolutePath()); + //initialize total storage on device + mTotalMemory = (long)mDataFileStats.getBlockCount() * + mDataFileStats.getBlockSize(); + mStorageLowIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_LOW); + mStorageLowIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + mStorageOkIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_OK); + mStorageOkIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + mStorageFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_FULL); + mStorageFullIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + mStorageNotFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_NOT_FULL); + mStorageNotFullIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + } + + @Override + public void onStart() { + // cache storage thresholds + final StorageManager sm = StorageManager.from(getContext()); + mMemLowThreshold = sm.getStorageLowBytes(DATA_PATH); + mMemFullThreshold = sm.getStorageFullBytes(DATA_PATH); + + mMemCacheStartTrimThreshold = ((mMemLowThreshold*3)+mMemFullThreshold)/4; + mMemCacheTrimToThreshold = mMemLowThreshold + + ((mMemLowThreshold-mMemCacheStartTrimThreshold)*2); + mFreeMemAfterLastCacheClear = mTotalMemory; + checkMemory(true); + + mCacheFileDeletedObserver = new CacheFileDeletedObserver(); + mCacheFileDeletedObserver.startWatching(); + + publishBinderService(SERVICE, mRemoteService); + publishLocalService(DeviceStorageMonitorInternal.class, mLocalService); + } + + private final DeviceStorageMonitorInternal mLocalService = new DeviceStorageMonitorInternal() { + @Override + public void checkMemory() { + // force an early check + postCheckMemoryMsg(true, 0); + } + + @Override + public boolean isMemoryLow() { + return mLowMemFlag; + } + + @Override + public long getMemoryLowThreshold() { + return mMemLowThreshold; + } + }; + + private final IBinder mRemoteService = new Binder() { + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + + pw.println("Permission Denial: can't dump " + SERVICE + " from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + return; + } + + dumpImpl(pw); + } + }; + + void dumpImpl(PrintWriter pw) { + final Context context = getContext(); + + pw.println("Current DeviceStorageMonitor state:"); + + pw.print(" mFreeMem="); pw.print(Formatter.formatFileSize(context, mFreeMem)); + pw.print(" mTotalMemory="); + pw.println(Formatter.formatFileSize(context, mTotalMemory)); + + pw.print(" mFreeMemAfterLastCacheClear="); + pw.println(Formatter.formatFileSize(context, mFreeMemAfterLastCacheClear)); + + pw.print(" mLastReportedFreeMem="); + pw.print(Formatter.formatFileSize(context, mLastReportedFreeMem)); + pw.print(" mLastReportedFreeMemTime="); + TimeUtils.formatDuration(mLastReportedFreeMemTime, SystemClock.elapsedRealtime(), pw); + pw.println(); + + pw.print(" mLowMemFlag="); pw.print(mLowMemFlag); + pw.print(" mMemFullFlag="); pw.println(mMemFullFlag); + + pw.print(" mClearSucceeded="); pw.print(mClearSucceeded); + pw.print(" mClearingCache="); pw.println(mClearingCache); + + pw.print(" mMemLowThreshold="); + pw.print(Formatter.formatFileSize(context, mMemLowThreshold)); + pw.print(" mMemFullThreshold="); + pw.println(Formatter.formatFileSize(context, mMemFullThreshold)); + + pw.print(" mMemCacheStartTrimThreshold="); + pw.print(Formatter.formatFileSize(context, mMemCacheStartTrimThreshold)); + pw.print(" mMemCacheTrimToThreshold="); + pw.println(Formatter.formatFileSize(context, mMemCacheTrimToThreshold)); + } + + /** + * This method sends a notification to NotificationManager to display + * an error dialog indicating low disk space and launch the Installer + * application + */ + private void sendNotification() { + final Context context = getContext(); + if(localLOGV) Slog.i(TAG, "Sending low memory notification"); + //log the event to event log with the amount of free storage(in bytes) left on the device + EventLog.writeEvent(EventLogTags.LOW_STORAGE, mFreeMem); + // Pack up the values and broadcast them to everyone + Intent lowMemIntent = new Intent(Environment.isExternalStorageEmulated() + ? Settings.ACTION_INTERNAL_STORAGE_SETTINGS + : Intent.ACTION_MANAGE_PACKAGE_STORAGE); + lowMemIntent.putExtra("memory", mFreeMem); + lowMemIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + NotificationManager mNotificationMgr = + (NotificationManager)context.getSystemService( + Context.NOTIFICATION_SERVICE); + CharSequence title = context.getText( + com.android.internal.R.string.low_internal_storage_view_title); + CharSequence details = context.getText( + com.android.internal.R.string.low_internal_storage_view_text); + PendingIntent intent = PendingIntent.getActivityAsUser(context, 0, lowMemIntent, 0, + null, UserHandle.CURRENT); + Notification notification = new Notification(); + notification.icon = com.android.internal.R.drawable.stat_notify_disk_full; + notification.tickerText = title; + notification.flags |= Notification.FLAG_NO_CLEAR; + notification.setLatestEventInfo(context, title, details, intent); + mNotificationMgr.notifyAsUser(null, LOW_MEMORY_NOTIFICATION_ID, notification, + UserHandle.ALL); + context.sendStickyBroadcastAsUser(mStorageLowIntent, UserHandle.ALL); + } + + /** + * Cancels low storage notification and sends OK intent. + */ + private void cancelNotification() { + final Context context = getContext(); + if(localLOGV) Slog.i(TAG, "Canceling low memory notification"); + NotificationManager mNotificationMgr = + (NotificationManager)context.getSystemService( + Context.NOTIFICATION_SERVICE); + //cancel notification since memory has been freed + mNotificationMgr.cancelAsUser(null, LOW_MEMORY_NOTIFICATION_ID, UserHandle.ALL); + + context.removeStickyBroadcastAsUser(mStorageLowIntent, UserHandle.ALL); + context.sendBroadcastAsUser(mStorageOkIntent, UserHandle.ALL); + } + + /** + * Send a notification when storage is full. + */ + private void sendFullNotification() { + if(localLOGV) Slog.i(TAG, "Sending memory full notification"); + getContext().sendStickyBroadcastAsUser(mStorageFullIntent, UserHandle.ALL); + } + + /** + * Cancels memory full notification and sends "not full" intent. + */ + private void cancelFullNotification() { + if(localLOGV) Slog.i(TAG, "Canceling memory full notification"); + getContext().removeStickyBroadcastAsUser(mStorageFullIntent, UserHandle.ALL); + getContext().sendBroadcastAsUser(mStorageNotFullIntent, UserHandle.ALL); + } + + private static class CacheFileDeletedObserver extends FileObserver { + public CacheFileDeletedObserver() { + super(Environment.getDownloadCacheDirectory().getAbsolutePath(), FileObserver.DELETE); + } + + @Override + public void onEvent(int event, String path) { + EventLogTags.writeCacheFileDeleted(path); + } + } +} diff --git a/services/java/com/android/server/twilight/TwilightListener.java b/services/java/com/android/server/twilight/TwilightListener.java new file mode 100644 index 000000000000..29ead445892e --- /dev/null +++ b/services/java/com/android/server/twilight/TwilightListener.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2013 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.twilight; + +public interface TwilightListener { + void onTwilightStateChanged(); +} \ No newline at end of file diff --git a/services/java/com/android/server/twilight/TwilightManager.java b/services/java/com/android/server/twilight/TwilightManager.java new file mode 100644 index 000000000000..b3de58b279af --- /dev/null +++ b/services/java/com/android/server/twilight/TwilightManager.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2013 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.twilight; + +import android.os.Handler; + +public interface TwilightManager { + void registerListener(TwilightListener listener, Handler handler); + TwilightState getCurrentState(); +} diff --git a/services/java/com/android/server/twilight/TwilightService.java b/services/java/com/android/server/twilight/TwilightService.java new file mode 100644 index 000000000000..8feb97b36f46 --- /dev/null +++ b/services/java/com/android/server/twilight/TwilightService.java @@ -0,0 +1,467 @@ +/* + * Copyright (C) 2012 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.twilight; + +import com.android.server.SystemService; +import com.android.server.TwilightCalculator; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.location.Criteria; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.SystemClock; +import android.text.format.DateUtils; +import android.text.format.Time; +import android.util.Slog; + +import java.util.ArrayList; +import java.util.Iterator; + +import libcore.util.Objects; + +/** + * Figures out whether it's twilight time based on the user's location. + * + * Used by the UI mode manager and other components to adjust night mode + * effects based on sunrise and sunset. + */ +public final class TwilightService extends SystemService { + static final String TAG = "TwilightService"; + static final boolean DEBUG = false; + static final String ACTION_UPDATE_TWILIGHT_STATE = + "com.android.server.action.UPDATE_TWILIGHT_STATE"; + + final Object mLock = new Object(); + + AlarmManager mAlarmManager; + LocationManager mLocationManager; + LocationHandler mLocationHandler; + + final ArrayList mListeners = + new ArrayList(); + + TwilightState mTwilightState; + + @Override + public void onStart() { + mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE); + mLocationManager = (LocationManager) getContext().getSystemService( + Context.LOCATION_SERVICE); + mLocationHandler = new LocationHandler(); + + IntentFilter filter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED); + filter.addAction(Intent.ACTION_TIME_CHANGED); + filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); + filter.addAction(ACTION_UPDATE_TWILIGHT_STATE); + getContext().registerReceiver(mUpdateLocationReceiver, filter); + + publishLocalService(TwilightManager.class, mService); + } + + private static class TwilightListenerRecord implements Runnable { + private final TwilightListener mListener; + private final Handler mHandler; + + public TwilightListenerRecord(TwilightListener listener, Handler handler) { + mListener = listener; + mHandler = handler; + } + + public void postUpdate() { + mHandler.post(this); + } + + @Override + public void run() { + mListener.onTwilightStateChanged(); + } + + } + + private final TwilightManager mService = new TwilightManager() { + /** + * Gets the current twilight state. + * + * @return The current twilight state, or null if no information is available. + */ + @Override + public TwilightState getCurrentState() { + synchronized (mLock) { + return mTwilightState; + } + } + + /** + * Listens for twilight time. + * + * @param listener The listener. + */ + @Override + public void registerListener(TwilightListener listener, Handler handler) { + synchronized (mLock) { + mListeners.add(new TwilightListenerRecord(listener, handler)); + + if (mListeners.size() == 1) { + mLocationHandler.enableLocationUpdates(); + } + } + } + }; + + private void setTwilightState(TwilightState state) { + synchronized (mLock) { + if (!Objects.equal(mTwilightState, state)) { + if (DEBUG) { + Slog.d(TAG, "Twilight state changed: " + state); + } + + mTwilightState = state; + + final int listenerLen = mListeners.size(); + for (int i = 0; i < listenerLen; i++) { + mListeners.get(i).postUpdate(); + } + } + } + } + + // The user has moved if the accuracy circles of the two locations don't overlap. + private static boolean hasMoved(Location from, Location to) { + if (to == null) { + return false; + } + + if (from == null) { + return true; + } + + // if new location is older than the current one, the device hasn't moved. + if (to.getElapsedRealtimeNanos() < from.getElapsedRealtimeNanos()) { + return false; + } + + // Get the distance between the two points. + float distance = from.distanceTo(to); + + // Get the total accuracy radius for both locations. + float totalAccuracy = from.getAccuracy() + to.getAccuracy(); + + // If the distance is greater than the combined accuracy of the two + // points then they can't overlap and hence the user has moved. + return distance >= totalAccuracy; + } + + private final class LocationHandler extends Handler { + private static final int MSG_ENABLE_LOCATION_UPDATES = 1; + private static final int MSG_GET_NEW_LOCATION_UPDATE = 2; + private static final int MSG_PROCESS_NEW_LOCATION = 3; + private static final int MSG_DO_TWILIGHT_UPDATE = 4; + + private static final long LOCATION_UPDATE_MS = 24 * DateUtils.HOUR_IN_MILLIS; + private static final long MIN_LOCATION_UPDATE_MS = 30 * DateUtils.MINUTE_IN_MILLIS; + private static final float LOCATION_UPDATE_DISTANCE_METER = 1000 * 20; + private static final long LOCATION_UPDATE_ENABLE_INTERVAL_MIN = 5000; + private static final long LOCATION_UPDATE_ENABLE_INTERVAL_MAX = + 15 * DateUtils.MINUTE_IN_MILLIS; + private static final double FACTOR_GMT_OFFSET_LONGITUDE = + 1000.0 * 360.0 / DateUtils.DAY_IN_MILLIS; + + private boolean mPassiveListenerEnabled; + private boolean mNetworkListenerEnabled; + private boolean mDidFirstInit; + private long mLastNetworkRegisterTime = -MIN_LOCATION_UPDATE_MS; + private long mLastUpdateInterval; + private Location mLocation; + private final TwilightCalculator mTwilightCalculator = new TwilightCalculator(); + + public void processNewLocation(Location location) { + Message msg = obtainMessage(MSG_PROCESS_NEW_LOCATION, location); + sendMessage(msg); + } + + public void enableLocationUpdates() { + sendEmptyMessage(MSG_ENABLE_LOCATION_UPDATES); + } + + public void requestLocationUpdate() { + sendEmptyMessage(MSG_GET_NEW_LOCATION_UPDATE); + } + + public void requestTwilightUpdate() { + sendEmptyMessage(MSG_DO_TWILIGHT_UPDATE); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_PROCESS_NEW_LOCATION: { + final Location location = (Location)msg.obj; + final boolean hasMoved = hasMoved(mLocation, location); + final boolean hasBetterAccuracy = mLocation == null + || location.getAccuracy() < mLocation.getAccuracy(); + if (DEBUG) { + Slog.d(TAG, "Processing new location: " + location + + ", hasMoved=" + hasMoved + + ", hasBetterAccuracy=" + hasBetterAccuracy); + } + if (hasMoved || hasBetterAccuracy) { + setLocation(location); + } + break; + } + + case MSG_GET_NEW_LOCATION_UPDATE: + if (!mNetworkListenerEnabled) { + // Don't do anything -- we are still trying to get a + // location. + return; + } + if ((mLastNetworkRegisterTime + MIN_LOCATION_UPDATE_MS) >= + SystemClock.elapsedRealtime()) { + // Don't do anything -- it hasn't been long enough + // since we last requested an update. + return; + } + + // Unregister the current location monitor, so we can + // register a new one for it to get an immediate update. + mNetworkListenerEnabled = false; + mLocationManager.removeUpdates(mEmptyLocationListener); + + // Fall through to re-register listener. + case MSG_ENABLE_LOCATION_UPDATES: + // enable network provider to receive at least location updates for a given + // distance. + boolean networkLocationEnabled; + try { + networkLocationEnabled = + mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER); + } catch (Exception e) { + // we may get IllegalArgumentException if network location provider + // does not exist or is not yet installed. + networkLocationEnabled = false; + } + if (!mNetworkListenerEnabled && networkLocationEnabled) { + mNetworkListenerEnabled = true; + mLastNetworkRegisterTime = SystemClock.elapsedRealtime(); + mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, + LOCATION_UPDATE_MS, 0, mEmptyLocationListener); + + if (!mDidFirstInit) { + mDidFirstInit = true; + if (mLocation == null) { + retrieveLocation(); + } + } + } + + // enable passive provider to receive updates from location fixes (gps + // and network). + boolean passiveLocationEnabled; + try { + passiveLocationEnabled = + mLocationManager.isProviderEnabled(LocationManager.PASSIVE_PROVIDER); + } catch (Exception e) { + // we may get IllegalArgumentException if passive location provider + // does not exist or is not yet installed. + passiveLocationEnabled = false; + } + + if (!mPassiveListenerEnabled && passiveLocationEnabled) { + mPassiveListenerEnabled = true; + mLocationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, + 0, LOCATION_UPDATE_DISTANCE_METER , mLocationListener); + } + + if (!(mNetworkListenerEnabled && mPassiveListenerEnabled)) { + mLastUpdateInterval *= 1.5; + if (mLastUpdateInterval == 0) { + mLastUpdateInterval = LOCATION_UPDATE_ENABLE_INTERVAL_MIN; + } else if (mLastUpdateInterval > LOCATION_UPDATE_ENABLE_INTERVAL_MAX) { + mLastUpdateInterval = LOCATION_UPDATE_ENABLE_INTERVAL_MAX; + } + sendEmptyMessageDelayed(MSG_ENABLE_LOCATION_UPDATES, mLastUpdateInterval); + } + break; + + case MSG_DO_TWILIGHT_UPDATE: + updateTwilightState(); + break; + } + } + + private void retrieveLocation() { + Location location = null; + final Iterator providers = + mLocationManager.getProviders(new Criteria(), true).iterator(); + while (providers.hasNext()) { + final Location lastKnownLocation = + mLocationManager.getLastKnownLocation(providers.next()); + // pick the most recent location + if (location == null || (lastKnownLocation != null && + location.getElapsedRealtimeNanos() < + lastKnownLocation.getElapsedRealtimeNanos())) { + location = lastKnownLocation; + } + } + + // In the case there is no location available (e.g. GPS fix or network location + // is not available yet), the longitude of the location is estimated using the timezone, + // latitude and accuracy are set to get a good average. + if (location == null) { + Time currentTime = new Time(); + currentTime.set(System.currentTimeMillis()); + double lngOffset = FACTOR_GMT_OFFSET_LONGITUDE * + (currentTime.gmtoff - (currentTime.isDst > 0 ? 3600 : 0)); + location = new Location("fake"); + location.setLongitude(lngOffset); + location.setLatitude(0); + location.setAccuracy(417000.0f); + location.setTime(System.currentTimeMillis()); + location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos()); + + if (DEBUG) { + Slog.d(TAG, "Estimated location from timezone: " + location); + } + } + + setLocation(location); + } + + private void setLocation(Location location) { + mLocation = location; + updateTwilightState(); + } + + private void updateTwilightState() { + if (mLocation == null) { + setTwilightState(null); + return; + } + + final long now = System.currentTimeMillis(); + + // calculate yesterday's twilight + mTwilightCalculator.calculateTwilight(now - DateUtils.DAY_IN_MILLIS, + mLocation.getLatitude(), mLocation.getLongitude()); + final long yesterdaySunset = mTwilightCalculator.mSunset; + + // calculate today's twilight + mTwilightCalculator.calculateTwilight(now, + mLocation.getLatitude(), mLocation.getLongitude()); + final boolean isNight = (mTwilightCalculator.mState == TwilightCalculator.NIGHT); + final long todaySunrise = mTwilightCalculator.mSunrise; + final long todaySunset = mTwilightCalculator.mSunset; + + // calculate tomorrow's twilight + mTwilightCalculator.calculateTwilight(now + DateUtils.DAY_IN_MILLIS, + mLocation.getLatitude(), mLocation.getLongitude()); + final long tomorrowSunrise = mTwilightCalculator.mSunrise; + + // set twilight state + TwilightState state = new TwilightState(isNight, yesterdaySunset, + todaySunrise, todaySunset, tomorrowSunrise); + if (DEBUG) { + Slog.d(TAG, "Updating twilight state: " + state); + } + setTwilightState(state); + + // schedule next update + long nextUpdate = 0; + if (todaySunrise == -1 || todaySunset == -1) { + // In the case the day or night never ends the update is scheduled 12 hours later. + nextUpdate = now + 12 * DateUtils.HOUR_IN_MILLIS; + } else { + // add some extra time to be on the safe side. + nextUpdate += DateUtils.MINUTE_IN_MILLIS; + + if (now > todaySunset) { + nextUpdate += tomorrowSunrise; + } else if (now > todaySunrise) { + nextUpdate += todaySunset; + } else { + nextUpdate += todaySunrise; + } + } + + if (DEBUG) { + Slog.d(TAG, "Next update in " + (nextUpdate - now) + " ms"); + } + + Intent updateIntent = new Intent(ACTION_UPDATE_TWILIGHT_STATE); + PendingIntent pendingIntent = PendingIntent.getBroadcast( + getContext(), 0, updateIntent, 0); + mAlarmManager.cancel(pendingIntent); + mAlarmManager.setExact(AlarmManager.RTC, nextUpdate, pendingIntent); + } + } + + private final BroadcastReceiver mUpdateLocationReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(intent.getAction()) + && !intent.getBooleanExtra("state", false)) { + // Airplane mode is now off! + mLocationHandler.requestLocationUpdate(); + return; + } + + // Time zone has changed or alarm expired. + mLocationHandler.requestTwilightUpdate(); + } + }; + + // A LocationListener to initialize the network location provider. The location updates + // are handled through the passive location provider. + private final LocationListener mEmptyLocationListener = new LocationListener() { + public void onLocationChanged(Location location) { + } + + public void onProviderDisabled(String provider) { + } + + public void onProviderEnabled(String provider) { + } + + public void onStatusChanged(String provider, int status, Bundle extras) { + } + }; + + private final LocationListener mLocationListener = new LocationListener() { + public void onLocationChanged(Location location) { + mLocationHandler.processNewLocation(location); + } + + public void onProviderDisabled(String provider) { + } + + public void onProviderEnabled(String provider) { + } + + public void onStatusChanged(String provider, int status, Bundle extras) { + } + }; +} diff --git a/services/java/com/android/server/twilight/TwilightState.java b/services/java/com/android/server/twilight/TwilightState.java new file mode 100644 index 000000000000..91e24d7d55bf --- /dev/null +++ b/services/java/com/android/server/twilight/TwilightState.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2013 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.twilight; + +import java.text.DateFormat; +import java.util.Date; + +/** + * Describes whether it is day or night. + * This object is immutable. + */ +public class TwilightState { + private final boolean mIsNight; + private final long mYesterdaySunset; + private final long mTodaySunrise; + private final long mTodaySunset; + private final long mTomorrowSunrise; + + TwilightState(boolean isNight, + long yesterdaySunset, + long todaySunrise, long todaySunset, + long tomorrowSunrise) { + mIsNight = isNight; + mYesterdaySunset = yesterdaySunset; + mTodaySunrise = todaySunrise; + mTodaySunset = todaySunset; + mTomorrowSunrise = tomorrowSunrise; + } + + /** + * Returns true if it is currently night time. + */ + public boolean isNight() { + return mIsNight; + } + + /** + * Returns the time of yesterday's sunset in the System.currentTimeMillis() timebase, + * or -1 if the sun never sets. + */ + public long getYesterdaySunset() { + return mYesterdaySunset; + } + + /** + * Returns the time of today's sunrise in the System.currentTimeMillis() timebase, + * or -1 if the sun never rises. + */ + public long getTodaySunrise() { + return mTodaySunrise; + } + + /** + * Returns the time of today's sunset in the System.currentTimeMillis() timebase, + * or -1 if the sun never sets. + */ + public long getTodaySunset() { + return mTodaySunset; + } + + /** + * Returns the time of tomorrow's sunrise in the System.currentTimeMillis() timebase, + * or -1 if the sun never rises. + */ + public long getTomorrowSunrise() { + return mTomorrowSunrise; + } + + @Override + public boolean equals(Object o) { + return o instanceof TwilightState && equals((TwilightState)o); + } + + public boolean equals(TwilightState other) { + return other != null + && mIsNight == other.mIsNight + && mYesterdaySunset == other.mYesterdaySunset + && mTodaySunrise == other.mTodaySunrise + && mTodaySunset == other.mTodaySunset + && mTomorrowSunrise == other.mTomorrowSunrise; + } + + @Override + public int hashCode() { + return 0; // don't care + } + + @Override + public String toString() { + DateFormat f = DateFormat.getDateTimeInstance(); + return "{TwilightState: isNight=" + mIsNight + + ", mYesterdaySunset=" + f.format(new Date(mYesterdaySunset)) + + ", mTodaySunrise=" + f.format(new Date(mTodaySunrise)) + + ", mTodaySunset=" + f.format(new Date(mTodaySunset)) + + ", mTomorrowSunrise=" + f.format(new Date(mTomorrowSunrise)) + + "}"; + } +} diff --git a/services/jni/Android.mk b/services/jni/Android.mk index 98e9b3085736..a98b1c3b95ae 100644 --- a/services/jni/Android.mk +++ b/services/jni/Android.mk @@ -8,7 +8,7 @@ LOCAL_SRC_FILES:= \ com_android_server_input_InputApplicationHandle.cpp \ com_android_server_input_InputManagerService.cpp \ com_android_server_input_InputWindowHandle.cpp \ - com_android_server_LightsService.cpp \ + com_android_server_lights_LightsService.cpp \ com_android_server_power_PowerManagerService.cpp \ com_android_server_SerialService.cpp \ com_android_server_SystemServer.cpp \ diff --git a/services/jni/com_android_server_LightsService.cpp b/services/jni/com_android_server_LightsService.cpp deleted file mode 100644 index 401e1aaf8851..000000000000 --- a/services/jni/com_android_server_LightsService.cpp +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (C) 2009 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. - */ - -#define LOG_TAG "LightsService" - -#include "jni.h" -#include "JNIHelp.h" -#include "android_runtime/AndroidRuntime.h" - -#include -#include -#include -#include - -#include - -namespace android -{ - -// These values must correspond with the LIGHT_ID constants in -// LightsService.java -enum { - LIGHT_INDEX_BACKLIGHT = 0, - LIGHT_INDEX_KEYBOARD = 1, - LIGHT_INDEX_BUTTONS = 2, - LIGHT_INDEX_BATTERY = 3, - LIGHT_INDEX_NOTIFICATIONS = 4, - LIGHT_INDEX_ATTENTION = 5, - LIGHT_INDEX_BLUETOOTH = 6, - LIGHT_INDEX_WIFI = 7, - LIGHT_COUNT -}; - -struct Devices { - light_device_t* lights[LIGHT_COUNT]; -}; - -static light_device_t* get_device(hw_module_t* module, char const* name) -{ - int err; - hw_device_t* device; - err = module->methods->open(module, name, &device); - if (err == 0) { - return (light_device_t*)device; - } else { - return NULL; - } -} - -static jint init_native(JNIEnv *env, jobject clazz) -{ - int err; - hw_module_t* module; - Devices* devices; - - devices = (Devices*)malloc(sizeof(Devices)); - - err = hw_get_module(LIGHTS_HARDWARE_MODULE_ID, (hw_module_t const**)&module); - if (err == 0) { - devices->lights[LIGHT_INDEX_BACKLIGHT] - = get_device(module, LIGHT_ID_BACKLIGHT); - devices->lights[LIGHT_INDEX_KEYBOARD] - = get_device(module, LIGHT_ID_KEYBOARD); - devices->lights[LIGHT_INDEX_BUTTONS] - = get_device(module, LIGHT_ID_BUTTONS); - devices->lights[LIGHT_INDEX_BATTERY] - = get_device(module, LIGHT_ID_BATTERY); - devices->lights[LIGHT_INDEX_NOTIFICATIONS] - = get_device(module, LIGHT_ID_NOTIFICATIONS); - devices->lights[LIGHT_INDEX_ATTENTION] - = get_device(module, LIGHT_ID_ATTENTION); - devices->lights[LIGHT_INDEX_BLUETOOTH] - = get_device(module, LIGHT_ID_BLUETOOTH); - devices->lights[LIGHT_INDEX_WIFI] - = get_device(module, LIGHT_ID_WIFI); - } else { - memset(devices, 0, sizeof(Devices)); - } - - return (jint)devices; -} - -static void finalize_native(JNIEnv *env, jobject clazz, int ptr) -{ - Devices* devices = (Devices*)ptr; - if (devices == NULL) { - return; - } - - free(devices); -} - -static void setLight_native(JNIEnv *env, jobject clazz, int ptr, - int light, int colorARGB, int flashMode, int onMS, int offMS, int brightnessMode) -{ - Devices* devices = (Devices*)ptr; - light_state_t state; - - if (light < 0 || light >= LIGHT_COUNT || devices->lights[light] == NULL) { - return ; - } - - memset(&state, 0, sizeof(light_state_t)); - state.color = colorARGB; - state.flashMode = flashMode; - state.flashOnMS = onMS; - state.flashOffMS = offMS; - state.brightnessMode = brightnessMode; - - { - ALOGD_IF_SLOW(50, "Excessive delay setting light"); - devices->lights[light]->set_light(devices->lights[light], &state); - } -} - -static JNINativeMethod method_table[] = { - { "init_native", "()I", (void*)init_native }, - { "finalize_native", "(I)V", (void*)finalize_native }, - { "setLight_native", "(IIIIIII)V", (void*)setLight_native }, -}; - -int register_android_server_LightsService(JNIEnv *env) -{ - return jniRegisterNativeMethods(env, "com/android/server/LightsService", - method_table, NELEM(method_table)); -} - -}; diff --git a/services/jni/com_android_server_lights_LightsService.cpp b/services/jni/com_android_server_lights_LightsService.cpp new file mode 100644 index 000000000000..ea03cfa1b147 --- /dev/null +++ b/services/jni/com_android_server_lights_LightsService.cpp @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2009 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. + */ + +#define LOG_TAG "LightsService" + +#include "jni.h" +#include "JNIHelp.h" +#include "android_runtime/AndroidRuntime.h" + +#include +#include +#include +#include + +#include + +namespace android +{ + +// These values must correspond with the LIGHT_ID constants in +// LightsService.java +enum { + LIGHT_INDEX_BACKLIGHT = 0, + LIGHT_INDEX_KEYBOARD = 1, + LIGHT_INDEX_BUTTONS = 2, + LIGHT_INDEX_BATTERY = 3, + LIGHT_INDEX_NOTIFICATIONS = 4, + LIGHT_INDEX_ATTENTION = 5, + LIGHT_INDEX_BLUETOOTH = 6, + LIGHT_INDEX_WIFI = 7, + LIGHT_COUNT +}; + +struct Devices { + light_device_t* lights[LIGHT_COUNT]; +}; + +static light_device_t* get_device(hw_module_t* module, char const* name) +{ + int err; + hw_device_t* device; + err = module->methods->open(module, name, &device); + if (err == 0) { + return (light_device_t*)device; + } else { + return NULL; + } +} + +static jint init_native(JNIEnv *env, jobject clazz) +{ + int err; + hw_module_t* module; + Devices* devices; + + devices = (Devices*)malloc(sizeof(Devices)); + + err = hw_get_module(LIGHTS_HARDWARE_MODULE_ID, (hw_module_t const**)&module); + if (err == 0) { + devices->lights[LIGHT_INDEX_BACKLIGHT] + = get_device(module, LIGHT_ID_BACKLIGHT); + devices->lights[LIGHT_INDEX_KEYBOARD] + = get_device(module, LIGHT_ID_KEYBOARD); + devices->lights[LIGHT_INDEX_BUTTONS] + = get_device(module, LIGHT_ID_BUTTONS); + devices->lights[LIGHT_INDEX_BATTERY] + = get_device(module, LIGHT_ID_BATTERY); + devices->lights[LIGHT_INDEX_NOTIFICATIONS] + = get_device(module, LIGHT_ID_NOTIFICATIONS); + devices->lights[LIGHT_INDEX_ATTENTION] + = get_device(module, LIGHT_ID_ATTENTION); + devices->lights[LIGHT_INDEX_BLUETOOTH] + = get_device(module, LIGHT_ID_BLUETOOTH); + devices->lights[LIGHT_INDEX_WIFI] + = get_device(module, LIGHT_ID_WIFI); + } else { + memset(devices, 0, sizeof(Devices)); + } + + return (jint)devices; +} + +static void finalize_native(JNIEnv *env, jobject clazz, int ptr) +{ + Devices* devices = (Devices*)ptr; + if (devices == NULL) { + return; + } + + free(devices); +} + +static void setLight_native(JNIEnv *env, jobject clazz, int ptr, + int light, int colorARGB, int flashMode, int onMS, int offMS, int brightnessMode) +{ + Devices* devices = (Devices*)ptr; + light_state_t state; + + if (light < 0 || light >= LIGHT_COUNT || devices->lights[light] == NULL) { + return ; + } + + memset(&state, 0, sizeof(light_state_t)); + state.color = colorARGB; + state.flashMode = flashMode; + state.flashOnMS = onMS; + state.flashOffMS = offMS; + state.brightnessMode = brightnessMode; + + { + ALOGD_IF_SLOW(50, "Excessive delay setting light"); + devices->lights[light]->set_light(devices->lights[light], &state); + } +} + +static JNINativeMethod method_table[] = { + { "init_native", "()I", (void*)init_native }, + { "finalize_native", "(I)V", (void*)finalize_native }, + { "setLight_native", "(IIIIIII)V", (void*)setLight_native }, +}; + +int register_android_server_LightsService(JNIEnv *env) +{ + return jniRegisterNativeMethods(env, "com/android/server/lights/LightsService", + method_table, NELEM(method_table)); +} + +}; -- cgit v1.2.3-59-g8ed1b