Introduce a Lifecycle for system services

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
diff --git a/services/java/com/android/server/AlarmManagerService.java b/services/java/com/android/server/AlarmManagerService.java
index 5ae9a6d..1fb164d 100644
--- a/services/java/com/android/server/AlarmManagerService.java
+++ b/services/java/com/android/server/AlarmManagerService.java
@@ -31,6 +31,7 @@
 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 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
+    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 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<InFlight> mInFlight = new ArrayList<InFlight>();
-    private final AlarmThread mWaitThread = new AlarmThread();
-    private final AlarmHandler mHandler = new AlarmHandler();
-    private ClockReceiver mClockReceiver;
+    int mBroadcastRefCount = 0;
+    PowerManager.WakeLock mWakeLock;
+    ArrayList<InFlight> mInFlight = new ArrayList<InFlight>();
+    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 @@
         }
     }
 
-    private final LinkedList<WakeupEvent> mRecentWakeups = new LinkedList<WakeupEvent>();
-    private final long RECENT_WAKEUP_PERIOD = 1000L * 60 * 60 * 24; // one day
+    final LinkedList<WakeupEvent> mRecentWakeups = new LinkedList<WakeupEvent>();
+    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 @@
     }
     
     // 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<Batch> mAlarmBatches = new ArrayList<Batch>();
+    static final long MIN_FUZZABLE_INTERVAL = 10000;
+    static final BatchTimeOrder sBatchOrder = new BatchTimeOrder();
+    final ArrayList<Batch> mAlarmBatches = new ArrayList<Batch>();
 
     static long convertToElapsed(long when, int type) {
         final boolean isRtc = (type == RTC || type == RTC_WAKEUP);
@@ -403,7 +401,7 @@
         }
     }
 
-    private static final class InFlight extends Intent {
+    static final class InFlight extends Intent {
         final PendingIntent mPendingIntent;
         final WorkSource mWorkSource;
         final Pair<String, ComponentName> mTarget;
@@ -427,7 +425,7 @@
         }
     }
 
-    private static final class FilterStats {
+    static final class FilterStats {
         final BroadcastStats mBroadcastStats;
         final Pair<String, ComponentName> mTarget;
 
@@ -443,7 +441,7 @@
         }
     }
     
-    private static final class BroadcastStats {
+    static final class BroadcastStats {
         final String mPackageName;
 
         long aggregateTime;
@@ -459,47 +457,48 @@
         }
     }
     
-    private final HashMap<String, BroadcastStats> mBroadcastStats
+    final HashMap<String, BroadcastStats> mBroadcastStats
             = new HashMap<String, BroadcastStats>();
     
-    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 @@
         }
     }
 
-    @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;
         }
 
-        set(type, triggerAtTime, windowLength, interval, operation, false, workSource);
+        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());
+            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 @@
         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;
-            }
-        }
-        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);
-            }
-        }
-    }
-
-    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));
+    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");
             }
 
-            TimeZone.setDefault(null);
+            setImpl(type, triggerAtTime, windowLength, interval, operation,
+                    false, workSource);
+        }
 
-            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);
+        @Override
+        public void setTime(long millis) {
+            getContext().enforceCallingOrSelfPermission(
+                    "android.permission.SET_TIME",
+                    "setTime");
+
+            SystemClock.setCurrentTimeMillis(millis);
         }
-    }
-    
-    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 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(operation) changed bounds; rebatching");
-            }
-            rebatchAllAlarmsLocked(true);
-            rescheduleKernelAlarmsLocked();
-        }
-    }
+        @Override
+        public void remove(PendingIntent operation) {
+            removeImpl(operation);
 
-    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);
-            }
         }
 
-        if (didRemove) {
-            if (DEBUG_BATCH) {
-                Slog.v(TAG, "remove(package) 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 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);
-            }
+            dumpImpl(pw);
         }
+    };
 
-        if (didRemove) {
-            if (DEBUG_BATCH) {
-                Slog.v(TAG, "remove(user) changed bounds; rebatching");
-            }
-            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;
-            }
-        }
-        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 @@
         }
     }
 
+    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<Alarm> list,
             String prefix, String label, long now) {
         for (int i=list.size()-1; i>=0; i--) {
@@ -1020,7 +1037,7 @@
     private native int waitForAlarm(int fd);
     private native int setKernelTimezone(int fd, int minuteswest);
 
-    private void triggerAlarmsLocked(ArrayList<Alarm> triggerList, long nowELAPSED, long nowRTC) {
+    void triggerAlarmsLocked(ArrayList<Alarm> 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 @@
                     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 @@
                         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 @@
                             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 @@
                         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 @@
             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 @@
             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 @@
             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 @@
             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 5f3f894..cc9055d 100644
--- a/services/java/com/android/server/BatteryService.java
+++ b/services/java/com/android/server/BatteryService.java
@@ -19,6 +19,8 @@
 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 @@
 
     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 @@
                     "DEVPATH=/devices/virtual/switch/invalid_charger");
         }
 
-        mBatteryPropertiesListener = new BatteryListener();
-
         IBinder b = ServiceManager.getService("batterypropreg");
-        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.
         }
@@ -688,7 +685,7 @@
     };
 
     private final class Led {
-        private final LightsService.Light mBatteryLight;
+        private final Light mBatteryLight;
 
         private final int mBatteryLowARGB;
         private final int mBatteryMediumARGB;
@@ -696,8 +693,8 @@
         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);
@@ -723,7 +720,7 @@
                     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
@@ -743,8 +740,14 @@
     }
 
     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/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index a996dbd..26424a5 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -28,7 +28,7 @@
 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/LocalServices.java b/services/java/com/android/server/LocalServices.java
new file mode 100644
index 0000000..deff79dc
--- /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<Class<?>, Object> sLocalServiceObjects =
+            new ArrayMap<Class<?>, 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> T getService(Class<T> 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 <T> void addService(Class<T> 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/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 3a1c747..662df92 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -17,6 +17,8 @@
 package com.android.server;
 
 import android.app.ActivityManagerNative;
+import android.app.IAlarmManager;
+import android.app.INotificationManager;
 import android.bluetooth.BluetoothAdapter;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -59,9 +61,12 @@
 import com.android.server.display.DisplayManagerService;
 import com.android.server.dreams.DreamManagerService;
 import com.android.server.input.InputManagerService;
+import com.android.server.lights.LightsManager;
+import com.android.server.lights.LightsService;
 import com.android.server.media.MediaRouterService;
 import com.android.server.net.NetworkPolicyManagerService;
 import com.android.server.net.NetworkStatsService;
+import com.android.server.notification.NotificationManagerService;
 import com.android.server.os.SchedulingPolicyService;
 import com.android.server.pm.Installer;
 import com.android.server.pm.PackageManagerService;
@@ -70,6 +75,10 @@
 import com.android.server.power.ShutdownThread;
 import com.android.server.print.PrintManagerService;
 import com.android.server.search.SearchManagerService;
+import com.android.server.statusbar.StatusBarManagerService;
+import com.android.server.storage.DeviceStorageMonitorService;
+import com.android.server.twilight.TwilightManager;
+import com.android.server.twilight.TwilightService;
 import com.android.server.usb.UsbService;
 import com.android.server.wallpaper.WallpaperManagerService;
 import com.android.server.wifi.WifiService;
@@ -131,12 +140,12 @@
         Installer installer = null;
         AccountManagerService accountManager = null;
         ContentService contentService = null;
-        LightsService lights = null;
+        LightsManager lights = null;
         PowerManagerService power = null;
         DisplayManagerService display = null;
         BatteryService battery = null;
         VibratorService vibrator = null;
-        AlarmManagerService alarm = null;
+        IAlarmManager alarm = null;
         MountService mountService = null;
         NetworkManagementService networkManagement = null;
         NetworkStatsService networkStats = null;
@@ -152,8 +161,7 @@
         DockObserver dock = null;
         UsbService usb = null;
         SerialService serial = null;
-        TwilightService twilight = null;
-        UiModeManagerService uiMode = null;
+        TwilightManager twilight = null;
         RecognitionManagerService recognition = null;
         NetworkTimeUpdateService networkTimeUpdater = null;
         CommonTimeManagementService commonTimeMgmtService = null;
@@ -203,6 +211,8 @@
             Slog.e("System", "************ Failure starting bootstrap service", e);
         }
 
+        final SystemServiceManager systemServiceManager = new SystemServiceManager(context);
+
         boolean disableStorage = SystemProperties.getBoolean("config.disable_storage", false);
         boolean disableMedia = SystemProperties.getBoolean("config.disable_media", false);
         boolean disableBluetooth = SystemProperties.getBoolean("config.disable_bluetooth", false);
@@ -278,8 +288,8 @@
             Slog.i(TAG, "System Content Providers");
             ActivityManagerService.installSystemProviders();
 
-            Slog.i(TAG, "Lights Service");
-            lights = new LightsService(context);
+            systemServiceManager.startService(LightsService.class);
+            lights = LocalServices.getService(LightsManager.class);
 
             Slog.i(TAG, "Battery Service");
             battery = new BatteryService(context, lights);
@@ -295,17 +305,16 @@
 
             // only initialize the power service after we have started the
             // lights service, content providers and the battery service.
-            power.init(context, lights, ActivityManagerService.self(), battery,
+            power.init(context, lights, battery,
                     BatteryStatsService.getService(),
                     ActivityManagerService.self().getAppOpsService(), display);
 
-            Slog.i(TAG, "Alarm Manager");
-            alarm = new AlarmManagerService(context);
-            ServiceManager.addService(Context.ALARM_SERVICE, alarm);
+            systemServiceManager.startService(AlarmManagerService.class);
+            alarm = IAlarmManager.Stub.asInterface(
+                    ServiceManager.getService(Context.ALARM_SERVICE));
 
             Slog.i(TAG, "Init Watchdog");
-            Watchdog.getInstance().init(context, battery, power, alarm,
-                    ActivityManagerService.self());
+            Watchdog.getInstance().init(context, ActivityManagerService.self());
             Watchdog.getInstance().addThread(wmHandler, "WindowManager thread");
 
             Slog.i(TAG, "Input Manager");
@@ -350,9 +359,9 @@
 
         DevicePolicyManagerService devicePolicy = null;
         StatusBarManagerService statusBar = null;
+        INotificationManager notification = null;
         InputMethodManagerService imm = null;
         AppWidgetService appWidget = null;
-        NotificationManagerService notification = null;
         WallpaperManagerService wallpaper = null;
         LocationManagerService location = null;
         CountryDetectorService countryDetector = null;
@@ -401,7 +410,7 @@
             ActivityManagerNative.getDefault().showBootMessage(
                     context.getResources().getText(
                             com.android.internal.R.string.android_upgrading_starting_apps),
-                            false);
+                    false);
         } catch (RemoteException e) {
         }
 
@@ -571,22 +580,12 @@
                 reportWtf("making Content Service ready", e);
             }
 
-            try {
-                Slog.i(TAG, "Notification Manager");
-                notification = new NotificationManagerService(context, statusBar, lights);
-                ServiceManager.addService(Context.NOTIFICATION_SERVICE, notification);
-                networkPolicy.bindNotificationManager(notification);
-            } catch (Throwable e) {
-                reportWtf("starting Notification Manager", e);
-            }
+            systemServiceManager.startService(NotificationManagerService.class);
+            notification = INotificationManager.Stub.asInterface(
+                    ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+            networkPolicy.bindNotificationManager(notification);
 
-            try {
-                Slog.i(TAG, "Device Storage Monitor");
-                ServiceManager.addService(DeviceStorageMonitorService.SERVICE,
-                        new DeviceStorageMonitorService(context));
-            } catch (Throwable e) {
-                reportWtf("starting DeviceStorageMonitor service", e);
-            }
+            systemServiceManager.startService(DeviceStorageMonitorService.class);
 
             if (!disableLocation) {
                 try {
@@ -685,20 +684,10 @@
                 }
             }
 
-            try {
-                Slog.i(TAG, "Twilight Service");
-                twilight = new TwilightService(context);
-            } catch (Throwable e) {
-                reportWtf("starting TwilightService", e);
-            }
+            systemServiceManager.startService(TwilightService.class);
+            twilight = LocalServices.getService(TwilightManager.class);
 
-            try {
-                Slog.i(TAG, "UI Mode Manager Service");
-                // Listen for UI mode changes
-                uiMode = new UiModeManagerService(context, twilight);
-            } catch (Throwable e) {
-                reportWtf("starting UiModeManagerService", e);
-            }
+            systemServiceManager.startService(UiModeManagerService.class);
 
             if (!disableNonCoreServices) {
                 try {
@@ -858,13 +847,7 @@
             }
         }
 
-        if (notification != null) {
-            try {
-                notification.systemReady();
-            } catch (Throwable e) {
-                reportWtf("making Notification Service ready", e);
-            }
-        }
+        systemServiceManager.startBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
 
         try {
             wm.systemReady();
@@ -913,8 +896,6 @@
         final ConnectivityService connectivityF = connectivity;
         final DockObserver dockF = dock;
         final UsbService usbF = usb;
-        final TwilightService twilightF = twilight;
-        final UiModeManagerService uiModeF = uiMode;
         final AppWidgetService appWidgetF = appWidget;
         final WallpaperManagerService wallpaperF = wallpaper;
         final InputMethodManagerService immF = imm;
@@ -992,16 +973,6 @@
                     reportWtf("making USB Service ready", e);
                 }
                 try {
-                    if (twilightF != null) twilightF.systemReady();
-                } catch (Throwable e) {
-                    reportWtf("makin Twilight Service ready", e);
-                }
-                try {
-                    if (uiModeF != null) uiModeF.systemReady();
-                } catch (Throwable e) {
-                    reportWtf("making UI Mode Service ready", e);
-                }
-                try {
                     if (recognitionF != null) recognitionF.systemReady();
                 } catch (Throwable e) {
                     reportWtf("making Recognition Service ready", e);
@@ -1010,6 +981,7 @@
 
                 // It is now okay to let the various system services start their
                 // third party code...
+                systemServiceManager.startBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
 
                 try {
                     if (appWidgetF != null) appWidgetF.systemRunning(safeMode);
@@ -1086,6 +1058,8 @@
                 } catch (Throwable e) {
                     reportWtf("Notifying MediaRouterService running", e);
                 }
+
+                systemServiceManager.startBootPhase(SystemService.PHASE_BOOT_COMPLETE);
             }
         });
 
diff --git a/services/java/com/android/server/SystemService.java b/services/java/com/android/server/SystemService.java
new file mode 100644
index 0000000..37afb4f
--- /dev/null
+++ b/services/java/com/android/server/SystemService.java
@@ -0,0 +1,151 @@
+/*
+ * 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.os.IBinder;
+import android.os.ServiceManager;
+
+/**
+ * System services respond to lifecycle events that help the services know what
+ */
+public abstract class SystemService {
+    /*
+     * Boot Phases
+     */
+    public static final int PHASE_SYSTEM_SERVICES_READY = 500;
+    public static final int PHASE_THIRD_PARTY_APPS_CAN_START = 600;
+    public static final int PHASE_BOOT_COMPLETE = 1000;
+
+    private SystemServiceManager mManager;
+    private Context mContext;
+
+    final void init(Context context, SystemServiceManager manager) {
+        mContext = context;
+        mManager = manager;
+        onCreate(context);
+    }
+
+    /**
+     * Services are not yet available. This is a good place to do setup work that does
+     * not require other services.
+     *
+     * @param context The system context.
+     */
+    public void onCreate(Context context) {}
+
+    /**
+     * Called when the dependencies listed in the @Service class-annotation are available
+     * and after the chosen start phase.
+     * When this method returns, the service should be published.
+     */
+    public abstract void onStart();
+
+    /**
+     * Called on each phase of the boot process. Phases before the service's start phase
+     * (as defined in the @Service annotation) are never received.
+     *
+     * @param phase The current boot phase.
+     */
+    public void onBootPhase(int phase) {}
+
+    /**
+     * Publish the service so it is accessible to other services and apps.
+     */
+    protected final void publishBinderService(String name, IBinder service) {
+        ServiceManager.addService(name, service);
+    }
+
+    /**
+     * Get a binder service by its name.
+     */
+    protected final IBinder getBinderService(String name) {
+        return ServiceManager.getService(name);
+    }
+
+    /**
+     * Publish the service so it is only accessible to the system process.
+     */
+    protected final <T> void publishLocalService(Class<T> type, T service) {
+        LocalServices.addService(type, service);
+    }
+
+    /**
+     * Get a local service by interface.
+     */
+    protected final <T> T getLocalService(Class<T> 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 0000000..648975a
--- /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<SystemService> mServices = new ArrayList<SystemService>();
+
+    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/UiModeManagerService.java b/services/java/com/android/server/UiModeManagerService.java
index 062be01..de912dc 100644
--- a/services/java/com/android/server/UiModeManagerService.java
+++ b/services/java/com/android/server/UiModeManagerService.java
@@ -34,9 +34,9 @@
 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 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 @@
     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;
 
-    private int mNightMode = UiModeManager.MODE_NIGHT_NO;
+    private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
+    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;
+
+    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 @@
         }
     };
 
-    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;
+    @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);
 
-        ServiceManager.addService(Context.UI_MODE_SERVICE, this);
-
-        mContext.registerReceiver(mDockModeReceiver,
+        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 @@
         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;
-            }
-        } 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);
         }
 
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            synchronized (mLock) {
-                if (isDoingNightModeLocked() && mNightMode != mode) {
-                    Settings.Secure.putInt(mContext.getContentResolver(),
-                            Settings.Secure.UI_NIGHT_MODE, mode);
-                    mNightMode = mode;
-                    updateLocked(0, 0);
+        @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 int getNightMode() {
+        @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);
+            }
+        }
+
+        @Override
+        public int getNightMode() {
+            synchronized (mLock) {
+                return mNightMode;
+            }
+        }
+
+        @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 @@
         }
     }
 
-    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 @@
                 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 @@
         } 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 @@
             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 @@
             // 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 @@
 
         // 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 @@
 
         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 @@
                 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 @@
         }
     }
 
-    private void updateTwilight() {
+    void updateTwilight() {
         synchronized (mLock) {
             if (isDoingNightModeLocked() && mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
                 updateComputedNightModeLocked();
@@ -573,36 +608,11 @@
     }
 
     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 e17f42d..b249953 100644
--- a/services/java/com/android/server/Watchdog.java
+++ b/services/java/com/android/server/Watchdog.java
@@ -80,9 +80,6 @@
     final ArrayList<HandlerChecker> mHandlerCheckers = new ArrayList<HandlerChecker>();
     final HandlerChecker mMonitorChecker;
     ContentResolver mResolver;
-    BatteryService mBattery;
-    PowerManagerService mPower;
-    AlarmManagerService mAlarm;
     ActivityManagerService mActivity;
 
     int mPhonePid;
@@ -234,13 +231,8 @@
                 "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 80e6e94..cb04835 100644
--- a/services/java/com/android/server/am/ServiceRecord.java
+++ b/services/java/com/android/server/am/ServiceRecord.java
@@ -18,7 +18,8 @@
 
 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 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 @@
                             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 0000000..b496b4c6
--- /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 0000000..2f20509
--- /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/LightsService.java b/services/java/com/android/server/lights/LightsService.java
similarity index 73%
rename from services/java/com/android/server/LightsService.java
rename to services/java/com/android/server/lights/LightsService.java
index 89bfcac..d814785 100644
--- a/services/java/com/android/server/LightsService.java
+++ b/services/java/com/android/server/lights/LightsService.java
@@ -14,59 +14,38 @@
  * limitations under the License.
  */
 
-package com.android.server;
+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.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 class LightsService extends SystemService {
+    static final String TAG = "LightsService";
+    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;
+    final LightImpl mLights[] = new LightImpl[LightsManager.LIGHT_ID_COUNT];
 
-    public static final int LIGHT_FLASH_NONE = 0;
-    public static final int LIGHT_FLASH_TIMED = 1;
-    public static final int LIGHT_FLASH_HARDWARE = 2;
+    private final class LightImpl extends Light {
 
-    /**
-     * 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) {
+        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;
@@ -75,23 +54,26 @@
             }
         }
 
+        @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) {
@@ -101,6 +83,7 @@
             }
         }
 
+        @Override
         public void turnOff() {
             synchronized (this) {
                 setLightLocked(0, LIGHT_FLASH_NONE, 0, 0, 0);
@@ -153,9 +136,10 @@
         }
 
         public void setFlashlightEnabled(boolean on) {
-            if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FLASHLIGHT)
+            final Context context = getContext();
+            if (context.checkCallingOrSelfPermission(android.Manifest.permission.FLASHLIGHT)
                     != PackageManager.PERMISSION_GRANTED &&
-                    mContext.checkCallingOrSelfPermission(android.Manifest.permission.HARDWARE_TEST)
+                    context.checkCallingOrSelfPermission(android.Manifest.permission.HARDWARE_TEST)
                     != PackageManager.PERMISSION_GRANTED) {
                 throw new SecurityException("Requires FLASHLIGHT or HARDWARE_TEST permission");
             }
@@ -172,31 +156,41 @@
         }
     };
 
-    LightsService(Context context) {
-
+    @Override
+    public void onCreate(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);
+        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();
     }
 
-    public Light getLight(int id) {
-        return mLights[id];
-    }
-
     private Handler mH = new Handler() {
         @Override
         public void handleMessage(Message msg) {
-            Light light = (Light)msg.obj;
+            LightImpl light = (LightImpl)msg.obj;
             light.stopFlashing();
         }
     };
@@ -204,10 +198,8 @@
     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,
+    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;
+    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 0000000..df2aaca
--- /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 0000000..92ffdcc
--- /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/NotificationManagerService.java b/services/java/com/android/server/notification/NotificationManagerService.java
similarity index 78%
rename from services/java/com/android/server/NotificationManagerService.java
rename to services/java/com/android/server/notification/NotificationManagerService.java
index 0438675..db4cf31d 100644
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/notification/NotificationManagerService.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server;
+package com.android.server.notification;
 
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
 import static org.xmlpull.v1.XmlPullParser.END_TAG;
@@ -47,7 +47,6 @@
 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;
@@ -56,9 +55,7 @@
 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;
@@ -78,6 +75,12 @@
 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;
 
@@ -99,66 +102,61 @@
 
 import libcore.io.IoUtils;
 
-
 /** {@hide} */
-public class NotificationManagerService extends INotificationManager.Stub
-{
-    private static final String TAG = "NotificationService";
-    private static final boolean DBG = false;
+public class NotificationManagerService extends SystemService {
+    static final String TAG = "NotificationService";
+    static final boolean DBG = false;
 
-    private static final int MAX_PACKAGE_NOTIFICATIONS = 50;
+    static final int MAX_PACKAGE_NOTIFICATIONS = 50;
 
     // message codes
-    private static final int MESSAGE_TIMEOUT = 2;
+    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
+    static final int LONG_DELAY = 3500; // 3.5 seconds
+    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
+    static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250};
+    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;
+    static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_NOTIFICATION;
+    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;
+    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
-    private static final int SCORE_INTERRUPTION_THRESHOLD =
+    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;
+    static final boolean ENABLE_BLOCKED_NOTIFICATIONS = true;
+    static final boolean ENABLE_BLOCKED_TOASTS = true;
 
-    private static final String ENABLED_NOTIFICATION_LISTENERS_SEPARATOR = ":";
+    static final String ENABLED_NOTIFICATION_LISTENERS_SEPARATOR = ":";
 
-    final Context mContext;
-    final IActivityManager mAm;
-    final UserManager mUserManager;
+    private IActivityManager mAm;
+    AudioManager mAudioManager;
+    StatusBarManagerInternal mStatusBar;
+    Vibrator mVibrator;
+
     final IBinder mForegroundToken = new Binder();
-
     private WorkerHandler mHandler;
-    private StatusBarManagerService mStatusBar;
-    private LightsService.Light mNotificationLight;
-    private LightsService.Light mAttentionLight;
 
+    private Light mNotificationLight;
+    Light mAttentionLight;
     private int mDefaultNotificationColor;
     private int mDefaultNotificationLedOn;
+
     private int mDefaultNotificationLedOff;
-
     private long[] mDefaultVibrationPattern;
+
     private long[] mFallbackVibrationPattern;
+    boolean mSystemReady;
 
-    private boolean mSystemReady;
-    private int mDisabledNotifications;
-
-    private NotificationRecord mSoundNotification;
-    private NotificationRecord mVibrateNotification;
-
-    private IAudioService mAudioService;
-    private Vibrator mVibrator;
+    int mDisabledNotifications;
+    NotificationRecord mSoundNotification;
+    NotificationRecord mVibrateNotification;
 
     // for enabling and disabling notification pulse behavior
     private boolean mScreenOn = true;
@@ -166,15 +164,15 @@
     private boolean mNotificationPulseEnabled;
 
     // used as a mutex for access to all active notifications & listeners
-    private final ArrayList<NotificationRecord> mNotificationList =
+    final ArrayList<NotificationRecord> mNotificationList =
             new ArrayList<NotificationRecord>();
 
-    private ArrayList<ToastRecord> mToastQueue;
+    final ArrayList<ToastRecord> mToastQueue = new ArrayList<ToastRecord>();
 
-    private ArrayList<NotificationRecord> mLights = new ArrayList<NotificationRecord>();
-    private NotificationRecord mLedNotification;
+    ArrayList<NotificationRecord> mLights = new ArrayList<NotificationRecord>();
+    NotificationRecord mLedNotification;
 
-    private final AppOpsManager mAppOps;
+    private AppOpsManager mAppOps;
 
     // contains connections to all connected listeners, including app services
     // and system listeners
@@ -202,9 +200,9 @@
     private static final String TAG_PACKAGE = "package";
     private static final String ATTR_NAME = "name";
 
-    private final ArrayList<NotificationScorer> mScorers = new ArrayList<NotificationScorer>();
+    final ArrayList<NotificationScorer> mScorers = new ArrayList<NotificationScorer>();
 
-    private class NotificationListenerInfo implements DeathRecipient {
+    private class NotificationListenerInfo implements IBinder.DeathRecipient {
         INotificationListener listener;
         ComponentName component;
         int userid;
@@ -262,7 +260,7 @@
         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);
+                unregisterListenerImpl(this.listener, this.userid);
             }
         }
 
@@ -400,12 +398,14 @@
                         tag = parser.getName();
                         if (type == START_TAG) {
                             if (TAG_BODY.equals(tag)) {
-                                version = Integer.parseInt(parser.getAttributeValue(null, ATTR_VERSION));
+                                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));
+                                        mBlockedPackages.add(
+                                                parser.getAttributeValue(null, ATTR_NAME));
                                     } else if (TAG_BLOCKED_PKGS.equals(tag) && type == END_TAG) {
                                         break;
                                     }
@@ -428,15 +428,6 @@
         }
     }
 
-    /**
-     * 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*().
@@ -450,21 +441,6 @@
         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;
 
@@ -490,57 +466,6 @@
         }
     }
 
-    /**
-     * 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<N; i++) {
-                    tmp[i] = mNotificationList.get(i).sbn;
-                }
-            }
-        }
-        return tmp;
-    }
-
-    /**
-     * System-only API for getting a list of recent (cleared, no longer shown) notifications.
-     *
-     * Requires ACCESS_NOTIFICATIONS which is signature|system.
-     */
-    @Override
-    public StatusBarNotification[] getHistoricalNotifications(String callingPkg, int count) {
-        // enforce() will ensure the calling uid has the correct permission
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_NOTIFICATIONS,
-                "NotificationManagerService.getHistoricalNotifications");
-
-        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 (mArchive) {
-                tmp = mArchive.getArray(count);
-            }
-        }
-        return tmp;
-    }
 
     /**
      * Remove notification access for any services that no longer exist.
@@ -548,12 +473,12 @@
     void disableNonexistentListeners() {
         int currentUser = ActivityManager.getCurrentUser();
         String flatIn = Settings.Secure.getStringForUser(
-                mContext.getContentResolver(),
+                getContext().getContentResolver(),
                 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
                 currentUser);
         if (!TextUtils.isEmpty(flatIn)) {
             if (DBG) Slog.v(TAG, "flat before: " + flatIn);
-            PackageManager pm = mContext.getPackageManager();
+            PackageManager pm = getContext().getPackageManager();
             List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser(
                     new Intent(NotificationListenerService.SERVICE_INTERFACE),
                     PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
@@ -589,7 +514,7 @@
             }
             if (DBG) Slog.v(TAG, "flat after: " + flatOut);
             if (!flatIn.equals(flatOut)) {
-                Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                Settings.Secure.putStringForUser(getContext().getContentResolver(),
                         Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
                         flatOut, currentUser);
             }
@@ -603,7 +528,7 @@
     void rebindListenerServices() {
         final int currentUser = ActivityManager.getCurrentUser();
         String flat = Settings.Secure.getStringForUser(
-                mContext.getContentResolver(),
+                getContext().getContentResolver(),
                 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
                 currentUser);
 
@@ -652,28 +577,6 @@
         }
     }
 
-    /**
-     * Register a listener binder directly with the notification manager.
-     *
-     * Only works with system callers. Apps should extend
-     * {@link android.service.notification.NotificationListenerService}.
-     */
-    @Override
-    public void registerListener(final INotificationListener listener,
-            final ComponentName component, final int userid) {
-        checkCallerIsSystem();
-
-        synchronized (mNotificationList) {
-            try {
-                NotificationListenerInfo info
-                        = new NotificationListenerInfo(listener, component, userid, true);
-                listener.asBinder().linkToDeath(info, 0);
-                mListeners.add(info);
-            } catch (RemoteException e) {
-                // already dead
-            }
-        }
-    }
 
     /**
      * Version of registerListener that takes the name of a
@@ -703,7 +606,7 @@
                     if (DBG) Slog.v(TAG, "    disconnecting old listener: " + info.listener);
                     mListeners.remove(i);
                     if (info.connection != null) {
-                        mContext.unbindService(info.connection);
+                        getContext().unbindService(info.connection);
                     }
                 }
             }
@@ -713,21 +616,25 @@
 
             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));
+
+            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 (!mContext.bindServiceAsUser(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(
+                                        NotificationListenerInfo info
+                                                = new NotificationListenerInfo(
                                                 mListener, name, userid, this);
                                         service.linkToDeath(info, 0);
                                         mListeners.add(info);
@@ -756,28 +663,6 @@
         }
     }
 
-    /**
-     * 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
@@ -794,7 +679,7 @@
                     mListeners.remove(i);
                     if (info.connection != null) {
                         try {
-                            mContext.unbindService(info.connection);
+                            getContext().unbindService(info.connection);
                         } catch (IllegalArgumentException ex) {
                             // something happened to the service: we think we have a connection
                             // but it's bogus.
@@ -809,7 +694,7 @@
     /**
      * asynchronously notify all listeners about a new notification
      */
-    private void notifyPostedLocked(NotificationRecord n) {
+    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) {
@@ -824,7 +709,7 @@
     /**
      * asynchronously notify all listeners about a removed notification
      */
-    private void notifyRemovedLocked(NotificationRecord n) {
+    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();
@@ -850,66 +735,7 @@
         throw new SecurityException("Disallowed call from unknown listener: " + listener);
     }
 
-    /**
-     * Allow an INotificationListener to simulate a "clear all" operation.
-     *
-     * {@see com.android.server.StatusBarManagerService.NotificationCallbacks#onClearAllNotifications}
-     *
-     * @param token The binder for the listener, to check that the caller is allowed
-     */
-    public void cancelAllNotificationsFromListener(INotificationListener token) {
-        NotificationListenerInfo info = checkListenerToken(token);
-        long identity = Binder.clearCallingIdentity();
-        try {
-            cancelAll(info.userid);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
 
-    /**
-     * Allow an INotificationListener to simulate clearing (dismissing) a single notification.
-     *
-     * {@see com.android.server.StatusBarManagerService.NotificationCallbacks#onNotificationClear}
-     *
-     * @param token The binder for the listener, to check that the caller is allowed
-     */
-    public void cancelNotificationFromListener(INotificationListener token, String pkg, String tag, int id) {
-        NotificationListenerInfo info = checkListenerToken(token);
-        long identity = Binder.clearCallingIdentity();
-        try {
-            cancelNotification(pkg, tag, id, 0,
-                    Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE,
-                    true,
-                    info.userid);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    /**
-     * Allow an INotificationListener to request the list of outstanding notifications seen by
-     * the current user. Useful when starting up, after which point the listener callbacks should
-     * be used.
-     *
-     * @param token The binder for the listener, to check that the caller is allowed
-     */
-    public StatusBarNotification[] getActiveNotificationsFromListener(INotificationListener token) {
-        NotificationListenerInfo info = checkListenerToken(token);
-
-        StatusBarNotification[] result = new StatusBarNotification[0];
-        ArrayList<StatusBarNotification> list = new ArrayList<StatusBarNotification>();
-        synchronized (mNotificationList) {
-            final int N = mNotificationList.size();
-            for (int i=0; i<N; i++) {
-                StatusBarNotification sbn = mNotificationList.get(i).sbn;
-                if (info.enabledAndUserMatches(sbn)) {
-                    list.add(sbn);
-                }
-            }
-        }
-        return list.toArray(result);
-    }
 
     // -- end of listener APIs --
 
@@ -992,8 +818,8 @@
             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());
+                    this.sbn.getPackageName(), this.sbn.getUser(), this.sbn.getId(),
+                    this.sbn.getTag(), this.sbn.getScore(), this.sbn.getNotification());
         }
     }
 
@@ -1031,9 +857,9 @@
         }
     }
 
-    private StatusBarManagerService.NotificationCallbacks mNotificationCallbacks
-            = new StatusBarManagerService.NotificationCallbacks() {
+    private final NotificationDelegate mNotificationDelegate = new NotificationDelegate() {
 
+        @Override
         public void onSetDisabled(int status) {
             synchronized (mNotificationList) {
                 mDisabledNotifications = status;
@@ -1041,7 +867,7 @@
                     // cancel whatever's going on
                     long identity = Binder.clearCallingIdentity();
                     try {
-                        final IRingtonePlayer player = mAudioService.getRingtonePlayer();
+                        final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
                         if (player != null) {
                             player.stopAsync();
                         }
@@ -1060,12 +886,14 @@
             }
         }
 
+        @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.
@@ -1074,6 +902,7 @@
                     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.
@@ -1082,6 +911,7 @@
                 true, ActivityManager.getCurrentUser());
         }
 
+        @Override
         public void onPanelRevealed() {
             synchronized (mNotificationList) {
                 // sound
@@ -1089,7 +919,7 @@
 
                 long identity = Binder.clearCallingIdentity();
                 try {
-                    final IRingtonePlayer player = mAudioService.getRingtonePlayer();
+                    final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
                     if (player != null) {
                         player.stopAsync();
                     }
@@ -1114,6 +944,7 @@
             }
         }
 
+        @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
@@ -1168,7 +999,7 @@
                     if (packageChanged) {
                         // We cancel notifications for packages which have just been disabled
                         try {
-                            final int enabled = mContext.getPackageManager()
+                            final int enabled = getContext().getPackageManager()
                                     .getApplicationEnabledSetting(pkgName);
                             if (enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED
                                     || enabled == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
@@ -1244,7 +1075,7 @@
         }
 
         void observe() {
-            ContentResolver resolver = mContext.getContentResolver();
+            ContentResolver resolver = getContext().getContentResolver();
             resolver.registerContentObserver(NOTIFICATION_LIGHT_PULSE_URI,
                     false, this, UserHandle.USER_ALL);
             resolver.registerContentObserver(ENABLED_NOTIFICATION_LISTENERS_URI,
@@ -1257,7 +1088,7 @@
         }
 
         public void update(Uri uri) {
-            ContentResolver resolver = mContext.getContentResolver();
+            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;
@@ -1287,28 +1118,24 @@
         return out;
     }
 
-    NotificationManagerService(Context context, StatusBarManagerService statusBar,
-            LightsService lights)
-    {
-        super();
-        mContext = context;
-        mVibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
+    @Override
+    public void onStart() {
         mAm = ActivityManagerNative.getDefault();
-        mUserManager = (UserManager)context.getSystemService(Context.USER_SERVICE);
-        mToastQueue = new ArrayList<ToastRecord>();
-        mHandler = new WorkerHandler();
+        mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
+        mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
 
-        mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
+        mHandler = new WorkerHandler();
 
         importOldBlockDb();
 
-        mStatusBar = statusBar;
-        statusBar.setNotificationCallbacks(mNotificationCallbacks);
+        mStatusBar = getLocalService(StatusBarManagerInternal.class);
+        mStatusBar.setNotificationDelegate(mNotificationDelegate);
 
-        mNotificationLight = lights.getLight(LightsService.LIGHT_ID_NOTIFICATIONS);
-        mAttentionLight = lights.getLight(LightsService.LIGHT_ID_ATTENTION);
+        final LightsManager lights = getLocalService(LightsManager.class);
+        mNotificationLight = lights.getLight(LightsManager.LIGHT_ID_NOTIFICATIONS);
+        mAttentionLight = lights.getLight(LightsManager.LIGHT_ID_ATTENTION);
 
-        Resources resources = mContext.getResources();
+        Resources resources = getContext().getResources();
         mDefaultNotificationColor = resources.getColor(
                 R.color.config_defaultNotificationColor);
         mDefaultNotificationLedOn = resources.getInteger(
@@ -1330,7 +1157,7 @@
         // 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(),
+        if (0 == Settings.Global.getInt(getContext().getContentResolver(),
                     Settings.Global.DEVICE_PROVISIONED, 0)) {
             mDisabledNotifications = StatusBarManager.DISABLE_NOTIFICATION_ALERTS;
         }
@@ -1343,7 +1170,7 @@
         filter.addAction(Intent.ACTION_USER_PRESENT);
         filter.addAction(Intent.ACTION_USER_STOPPED);
         filter.addAction(Intent.ACTION_USER_SWITCHED);
-        mContext.registerReceiver(mIntentReceiver, filter);
+        getContext().registerReceiver(mIntentReceiver, filter);
         IntentFilter pkgFilter = new IntentFilter();
         pkgFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
         pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
@@ -1351,9 +1178,9 @@
         pkgFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
         pkgFilter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
         pkgFilter.addDataScheme("package");
-        mContext.registerReceiver(mIntentReceiver, pkgFilter);
+        getContext().registerReceiver(mIntentReceiver, pkgFilter);
         IntentFilter sdFilter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
-        mContext.registerReceiver(mIntentReceiver, sdFilter);
+        getContext().registerReceiver(mIntentReceiver, sdFilter);
 
         mSettingsObserver = new SettingsObserver(mHandler);
         mSettingsObserver.observe();
@@ -1363,9 +1190,9 @@
                 R.array.config_notificationScorers);
         for (String scorerName : notificationScorerNames) {
             try {
-                Class<?> scorerClass = mContext.getClassLoader().loadClass(scorerName);
+                Class<?> scorerClass = getContext().getClassLoader().loadClass(scorerName);
                 NotificationScorer scorer = (NotificationScorer) scorerClass.newInstance();
-                scorer.initialize(mContext);
+                scorer.initialize(getContext());
                 mScorers.add(scorer);
             } catch (ClassNotFoundException e) {
                 Slog.w(TAG, "Couldn't find scorer " + scorerName + ".", e);
@@ -1375,6 +1202,9 @@
                 Slog.w(TAG, "Problem accessing scorer " + scorerName + ".", e);
             }
         }
+
+        publishBinderService(Context.NOTIFICATION_SERVICE, mService);
+        publishLocalService(NotificationManagerInternal.class, mInternalService);
     }
 
     /**
@@ -1383,12 +1213,12 @@
     private void importOldBlockDb() {
         loadBlockDb();
 
-        PackageManager pm = mContext.getPackageManager();
+        PackageManager pm = getContext().getPackageManager();
         for (String pkg : mBlockedPackages) {
             PackageInfo info = null;
             try {
                 info = pm.getPackageInfo(pkg, 0);
-                setNotificationsEnabledForPackage(pkg, info.applicationInfo.uid, false);
+                setNotificationsEnabledForPackageImpl(pkg, info.applicationInfo.uid, false);
             } catch (NameNotFoundException e) {
                 // forget you
             }
@@ -1399,244 +1229,421 @@
         }
     }
 
-    void systemReady() {
-        mAudioService = IAudioService.Stub.asInterface(
-                ServiceManager.getService(Context.AUDIO_SERVICE));
+    @Override
+    public void onBootPhase(int phase) {
+        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+            // no beeping until we're basically done booting
+            mSystemReady = true;
 
-        // 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();
-    }
-
-    // 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<N; i++) {
-                             final ToastRecord r = mToastQueue.get(i);
-                             if (r.pkg.equals(pkg)) {
-                                 count++;
-                                 if (count >= 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);
-            }
+            // make sure our listener services are properly bound
+            rebindListenerServices();
         }
     }
 
-    public void cancelToast(String pkg, ITransientNotification callback) {
-        Slog.i(TAG, "cancelToast pkg=" + pkg + " callback=" + callback);
+    void setNotificationsEnabledForPackageImpl(String pkg, int uid, boolean enabled) {
+        Slog.v(TAG, (enabled?"en":"dis") + "abling notifications for " + pkg);
 
-        if (pkg == null || callback == null) {
-            Slog.e(TAG, "Not cancelling notification. pkg=" + pkg + " callback=" + callback);
-            return ;
-        }
+        mAppOps.setMode(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg,
+                enabled ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED);
 
-        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);
-            }
+        // 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 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 final IBinder mService = new INotificationManager.Stub() {
+        // Toasts
+        // ============================================================================
 
-    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<ToastRecord> list = mToastQueue;
-        int len = list.size();
-        for (int i=0; i<len; i++) {
-            ToastRecord r = list.get(i);
-            if (r.pkg.equals(pkg) && r.callback.asBinder() == cbak) {
-                return i;
-            }
-        }
-        return -1;
-    }
-
-    // lock on mToastQueue
-    private void keepProcessAliveLocked(int pid)
-    {
-        int toastCount = 0; // toasts from this pid
-        ArrayList<ToastRecord> list = mToastQueue;
-        int N = list.size();
-        for (int i=0; i<N; i++) {
-            ToastRecord r = list.get(i);
-            if (r.pid == pid) {
-                toastCount++;
-            }
-        }
-        try {
-            mAm.setProcessForeground(mForegroundToken, pid, toastCount > 0);
-        } catch (RemoteException e) {
-            // Shouldn't happen.
-        }
-    }
-
-    private final class WorkerHandler extends Handler
-    {
         @Override
-        public void handleMessage(Message msg)
+        public void enqueueToast(String pkg, ITransientNotification callback, int duration)
         {
-            switch (msg.what)
-            {
-                case MESSAGE_TIMEOUT:
-                    handleTimeout((ToastRecord)msg.obj);
-                    break;
+            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<N; i++) {
+                                 final ToastRecord r = mToastQueue.get(i);
+                                 if (r.pkg.equals(pkg)) {
+                                     count++;
+                                     if (count >= 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<N; i++) {
+                        tmp[i] = mNotificationList.get(i).sbn;
+                    }
+                }
+            }
+            return tmp;
+        }
+
+        /**
+         * System-only API for getting a list of recent (cleared, no longer shown) notifications.
+         *
+         * Requires ACCESS_NOTIFICATIONS which is signature|system.
+         */
+        @Override
+        public StatusBarNotification[] getHistoricalNotifications(String callingPkg, int count) {
+            // enforce() will ensure the calling uid has the correct permission
+            getContext().enforceCallingOrSelfPermission(
+                    android.Manifest.permission.ACCESS_NOTIFICATIONS,
+                    "NotificationManagerService.getHistoricalNotifications");
+
+            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 (mArchive) {
+                    tmp = mArchive.getArray(count);
+                }
+            }
+            return tmp;
+        }
+
+        /**
+         * Register a listener binder directly with the notification manager.
+         *
+         * Only works with system callers. Apps should extend
+         * {@link android.service.notification.NotificationListenerService}.
+         */
+        @Override
+        public void registerListener(final INotificationListener listener,
+                final ComponentName component, final int userid) {
+            checkCallerIsSystem();
+            registerListenerImpl(listener, component, userid);
+        }
+
+        /**
+         * 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
+            unregisterListenerImpl(listener, userid);
+        }
+
+        /**
+         * Allow an INotificationListener to simulate a "clear all" operation.
+         *
+         * {@see com.android.server.StatusBarManagerService.NotificationCallbacks#onClearAllNotifications}
+         *
+         * @param token The binder for the listener, to check that the caller is allowed
+         */
+        @Override
+        public void cancelAllNotificationsFromListener(INotificationListener token) {
+            NotificationListenerInfo info = checkListenerToken(token);
+            long identity = Binder.clearCallingIdentity();
+            try {
+                cancelAll(info.userid);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        /**
+         * Allow an INotificationListener to simulate clearing (dismissing) a single notification.
+         *
+         * {@see com.android.server.StatusBarManagerService.NotificationCallbacks#onNotificationClear}
+         *
+         * @param token The binder for the listener, to check that the caller is allowed
+         */
+        @Override
+        public void cancelNotificationFromListener(INotificationListener token, String pkg,
+                String tag, int id) {
+            NotificationListenerInfo info = checkListenerToken(token);
+            long identity = Binder.clearCallingIdentity();
+            try {
+                cancelNotification(pkg, tag, id, 0,
+                        Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE,
+                        true,
+                        info.userid);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        /**
+         * Allow an INotificationListener to request the list of outstanding notifications seen by
+         * the current user. Useful when starting up, after which point the listener callbacks
+         * should be used.
+         *
+         * @param token The binder for the listener, to check that the caller is allowed
+         */
+        @Override
+        public StatusBarNotification[] getActiveNotificationsFromListener(
+                INotificationListener token) {
+            NotificationListenerInfo info = checkListenerToken(token);
+
+            StatusBarNotification[] result = new StatusBarNotification[0];
+            ArrayList<StatusBarNotification> list = new ArrayList<StatusBarNotification>();
+            synchronized (mNotificationList) {
+                final int N = mNotificationList.size();
+                for (int i=0; i<N; i++) {
+                    StatusBarNotification sbn = mNotificationList.get(i).sbn;
+                    if (info.enabledAndUserMatches(sbn)) {
+                        list.add(sbn);
+                    }
+                }
+            }
+            return list.toArray(result);
+        }
+
+        @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 NotificationManager from from pid="
+                        + Binder.getCallingPid()
+                        + ", uid=" + Binder.getCallingUid());
+                return;
+            }
+
+            dumpImpl(pw);
+        }
+    };
+
+    void dumpImpl(PrintWriter pw) {
+        pw.println("Current Notification Manager state:");
+
+        pw.println("  Listeners (" + mEnabledListenersForCurrentUser.size()
+                + ") enabled for current user:");
+        for (ComponentName cmpt : mEnabledListenersForCurrentUser) {
+            pw.println("    " + cmpt);
+        }
+
+        pw.println("  Live listeners (" + mListeners.size() + "):");
+        for (NotificationListenerInfo info : mListeners) {
+            pw.println("    " + info.component
+                    + " (user " + info.userid + "): " + info.listener
+                    + (info.isSystem?" SYSTEM":""));
+        }
+
+        int N;
+
+        synchronized (mToastQueue) {
+            N = mToastQueue.size();
+            if (N > 0) {
+                pw.println("  Toast Queue:");
+                for (int i=0; i<N; i++) {
+                    mToastQueue.get(i).dump(pw, "    ");
+                }
+                pw.println("  ");
+            }
+
+        }
+
+        synchronized (mNotificationList) {
+            N = mNotificationList.size();
+            if (N > 0) {
+                pw.println("  Notification List:");
+                for (int i=0; i<N; i++) {
+                    mNotificationList.get(i).dump(pw, "    ", getContext());
+                }
+                pw.println("  ");
+            }
+
+            N = mLights.size();
+            if (N > 0) {
+                pw.println("  Lights List:");
+                for (int i=0; i<N; i++) {
+                    pw.println("    " + mLights.get(i));
+                }
+                pw.println("  ");
+            }
+
+            pw.println("  mSoundNotification=" + mSoundNotification);
+            pw.println("  mVibrateNotification=" + mVibrateNotification);
+            pw.println("  mDisabledNotifications=0x"
+                    + Integer.toHexString(mDisabledNotifications));
+            pw.println("  mSystemReady=" + mSystemReady);
+            pw.println("  mArchive=" + mArchive.toString());
+            Iterator<StatusBarNotification> iter = mArchive.descendingIterator();
+            int i=0;
+            while (iter.hasNext()) {
+                pw.println("    " + iter.next());
+                if (++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);
+        }
+    };
 
-    // 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,
+    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)
-    {
+            int[] idOut, int incomingUserId) {
         if (DBG) {
-            Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id + " notification=" + notification);
+            Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id
+                    + " notification=" + notification);
         }
         checkCallerIsSystemOrSameApp(pkg);
         final boolean isSystemNotification = isUidSystem(callingUid) || ("android".equals(pkg));
@@ -1787,23 +1794,21 @@
                     if (notification.icon != 0) {
                         if (old != null && old.statusBarKey != null) {
                             r.statusBarKey = old.statusBarKey;
-                            long identity = Binder.clearCallingIdentity();
+                            final long identity = Binder.clearCallingIdentity();
                             try {
                                 mStatusBar.updateNotification(r.statusBarKey, n);
-                            }
-                            finally {
+                            } finally {
                                 Binder.restoreCallingIdentity(identity);
                             }
                         } else {
-                            long identity = Binder.clearCallingIdentity();
+                            final long identity = Binder.clearCallingIdentity();
                             try {
                                 r.statusBarKey = mStatusBar.addNotification(n);
                                 if ((n.getNotification().flags & Notification.FLAG_SHOW_LIGHTS) != 0
                                         && canInterrupt) {
                                     mAttentionLight.pulse();
                                 }
-                            }
-                            finally {
+                            } finally {
                                 Binder.restoreCallingIdentity(identity);
                             }
                         }
@@ -1816,33 +1821,32 @@
                     } else {
                         Slog.e(TAG, "Not posting notification with icon==0: " + notification);
                         if (old != null && old.statusBarKey != null) {
-                            long identity = Binder.clearCallingIdentity();
+                            final long identity = Binder.clearCallingIdentity();
                             try {
                                 mStatusBar.removeNotification(old.statusBarKey);
-                            }
-                            finally {
+                            } 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
+                        // 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)
+                    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);
+                            && mSystemReady
+                            && mAudioManager != null) {
 
                         // sound
 
@@ -1861,7 +1865,7 @@
                             soundUri = Settings.System.DEFAULT_NOTIFICATION_URI;
 
                             // check to see if the default notification sound is silent
-                            ContentResolver resolver = mContext.getContentResolver();
+                            ContentResolver resolver = getContext().getContentResolver();
                             hasValidSound = Settings.System.getString(resolver,
                                    Settings.System.NOTIFICATION_SOUND) != null;
                         } else if (notification.sound != null) {
@@ -1870,7 +1874,8 @@
                         }
 
                         if (hasValidSound) {
-                            boolean looping = (notification.flags & Notification.FLAG_INSISTENT) != 0;
+                            boolean looping =
+                                    (notification.flags & Notification.FLAG_INSISTENT) != 0;
                             int audioStreamType;
                             if (notification.audioStreamType >= 0) {
                                 audioStreamType = notification.audioStreamType;
@@ -1880,11 +1885,12 @@
                             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()) {
+                            if ((mAudioManager.getStreamVolume(audioStreamType) != 0)
+                                    && !mAudioManager.isAudioFocusExclusive()) {
                                 final long identity = Binder.clearCallingIdentity();
                                 try {
-                                    final IRingtonePlayer player = mAudioService.getRingtonePlayer();
+                                    final IRingtonePlayer player =
+                                            mAudioManager.getRingtonePlayer();
                                     if (player != null) {
                                         player.playAsync(soundUri, user, looping, audioStreamType);
                                     }
@@ -1904,7 +1910,7 @@
                         final boolean convertSoundToVibration =
                                    !hasCustomVibrate
                                 && hasValidSound
-                                && (audioManager.getRingerMode()
+                                && (mAudioManager.getRingerMode()
                                            == AudioManager.RINGER_MODE_VIBRATE);
 
                         // The DEFAULT_VIBRATE flag trumps any custom vibration AND the fallback.
@@ -1912,7 +1918,7 @@
                                 (notification.defaults & Notification.DEFAULT_VIBRATE) != 0;
 
                         if ((useDefaultVibrate || convertSoundToVibration || hasCustomVibrate)
-                                && !(audioManager.getRingerMode()
+                                && !(mAudioManager.getRingerMode()
                                         == AudioManager.RINGER_MODE_SILENT)) {
                             mVibrateNotification = r;
 
@@ -1966,8 +1972,158 @@
         idOut[0] = id;
     }
 
-    private void sendAccessibilityEvent(Notification notification, CharSequence packageName) {
-        AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
+     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<ToastRecord> list = mToastQueue;
+        int len = list.size();
+        for (int i=0; i<len; i++) {
+            ToastRecord r = list.get(i);
+            if (r.pkg.equals(pkg) && r.callback.asBinder() == cbak) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    // lock on mToastQueue
+    void keepProcessAliveLocked(int pid)
+    {
+        int toastCount = 0; // toasts from this pid
+        ArrayList<ToastRecord> list = mToastQueue;
+        int N = list.size();
+        for (int i=0; i<N; i++) {
+            ToastRecord r = list.get(i);
+            if (r.pid == pid) {
+                toastCount++;
+            }
+        }
+        try {
+            mAm.setProcessForeground(mForegroundToken, pid, toastCount > 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;
         }
@@ -2001,11 +2157,10 @@
 
         // status bar
         if (r.getNotification().icon != 0) {
-            long identity = Binder.clearCallingIdentity();
+            final long identity = Binder.clearCallingIdentity();
             try {
                 mStatusBar.removeNotification(r.statusBarKey);
-            }
-            finally {
+            } finally {
                 Binder.restoreCallingIdentity(identity);
             }
             r.statusBarKey = null;
@@ -2017,7 +2172,7 @@
             mSoundNotification = null;
             final long identity = Binder.clearCallingIdentity();
             try {
-                final IRingtonePlayer player = mAudioService.getRingtonePlayer();
+                final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
                 if (player != null) {
                     player.stopAsync();
                 }
@@ -2053,7 +2208,7 @@
      * 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,
+    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
@@ -2146,26 +2301,7 @@
         }
     }
 
-    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.
@@ -2225,7 +2361,7 @@
     }
 
     // lock on mNotificationList
-    private void updateLightsLocked()
+    void updateLightsLocked()
     {
         // handle notification lights
         if (mLedNotification == null) {
@@ -2251,14 +2387,14 @@
             }
             if (mNotificationPulseEnabled) {
                 // pulse repeatedly
-                mNotificationLight.setFlashing(ledARGB, LightsService.LIGHT_FLASH_TIMED,
+                mNotificationLight.setFlashing(ledARGB, Light.LIGHT_FLASH_TIMED,
                         ledOnMS, ledOffMS);
             }
         }
     }
 
     // lock on mNotificationList
-    private int indexOfNotificationLocked(String pkg, String tag, int id, int userId)
+    int indexOfNotificationLocked(String pkg, String tag, int id, int userId)
     {
         ArrayList<NotificationRecord> list = mNotificationList;
         final int len = list.size();
@@ -2288,81 +2424,4 @@
             updateLightsLocked();
         }
     }
-
-    // ======================================================================
-    @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 NotificationManager from from pid="
-                    + Binder.getCallingPid()
-                    + ", uid=" + Binder.getCallingUid());
-            return;
-        }
-
-        pw.println("Current Notification Manager state:");
-
-        pw.println("  Listeners (" + mEnabledListenersForCurrentUser.size()
-                + ") enabled for current user:");
-        for (ComponentName cmpt : mEnabledListenersForCurrentUser) {
-            pw.println("    " + cmpt);
-        }
-
-        pw.println("  Live listeners (" + mListeners.size() + "):");
-        for (NotificationListenerInfo info : mListeners) {
-            pw.println("    " + info.component
-                    + " (user " + info.userid + "): " + info.listener
-                    + (info.isSystem?" SYSTEM":""));
-        }
-
-        int N;
-
-        synchronized (mToastQueue) {
-            N = mToastQueue.size();
-            if (N > 0) {
-                pw.println("  Toast Queue:");
-                for (int i=0; i<N; i++) {
-                    mToastQueue.get(i).dump(pw, "    ");
-                }
-                pw.println("  ");
-            }
-
-        }
-
-        synchronized (mNotificationList) {
-            N = mNotificationList.size();
-            if (N > 0) {
-                pw.println("  Notification List:");
-                for (int i=0; i<N; i++) {
-                    mNotificationList.get(i).dump(pw, "    ", mContext);
-                }
-                pw.println("  ");
-            }
-
-            N = mLights.size();
-            if (N > 0) {
-                pw.println("  Lights List:");
-                for (int i=0; i<N; i++) {
-                    pw.println("    " + mLights.get(i));
-                }
-                pw.println("  ");
-            }
-
-            pw.println("  mSoundNotification=" + mSoundNotification);
-            pw.println("  mVibrateNotification=" + mVibrateNotification);
-            pw.println("  mDisabledNotifications=0x" + Integer.toHexString(mDisabledNotifications));
-            pw.println("  mSystemReady=" + mSystemReady);
-            pw.println("  mArchive=" + mArchive.toString());
-            Iterator<StatusBarNotification> iter = mArchive.descendingIterator();
-            int i=0;
-            while (iter.hasNext()) {
-                pw.println("    " + iter.next());
-                if (++i >= 5) {
-                    if (iter.hasNext()) pw.println("    ...");
-                    break;
-                }
-            }
-
-        }
-    }
 }
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index 5761f6c..b11ebf5 100755
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -38,10 +38,10 @@
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.XmlUtils;
-import com.android.server.DeviceStorageMonitorService;
 import com.android.server.EventLogTags;
 import com.android.server.IntentResolver;
 
+import com.android.server.LocalServices;
 import com.android.server.Watchdog;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -92,6 +92,7 @@
 import android.content.pm.VerifierDeviceIdentity;
 import android.content.pm.VerifierInfo;
 import android.content.res.Resources;
+import android.hardware.display.DisplayManager;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
@@ -127,7 +128,6 @@
 import android.util.SparseArray;
 import android.util.Xml;
 import android.view.Display;
-import android.view.WindowManager;
 
 import java.io.BufferedOutputStream;
 import java.io.File;
@@ -162,6 +162,7 @@
 import libcore.io.StructStat;
 
 import com.android.internal.R;
+import com.android.server.storage.DeviceStorageMonitorInternal;
 
 /**
  * Keep track of all those .apks everywhere.
@@ -1067,6 +1068,12 @@
         return res;
     }
 
+    private static void getDefaultDisplayMetrics(Context context, DisplayMetrics metrics) {
+        DisplayManager displayManager = (DisplayManager) context.getSystemService(
+                Context.DISPLAY_SERVICE);
+        displayManager.getDisplay(Display.DEFAULT_DISPLAY).getMetrics(metrics);
+    }
+
     public PackageManagerService(Context context, Installer installer,
             boolean factoryTest, boolean onlyCore) {
         EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_START,
@@ -1114,9 +1121,7 @@
 
         mInstaller = installer;
 
-        WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
-        Display d = wm.getDefaultDisplay();
-        d.getMetrics(mMetrics);
+        getDefaultDisplayMetrics(context, mMetrics);
 
         synchronized (mInstallLock) {
         // writer
@@ -7339,6 +7344,15 @@
             return pkgLite.recommendedInstallLocation;
         }
 
+        private long getMemoryLowThreshold() {
+            final DeviceStorageMonitorInternal
+                    dsm = LocalServices.getService(DeviceStorageMonitorInternal.class);
+            if (dsm == null) {
+                return 0L;
+            }
+            return dsm.getMemoryLowThreshold();
+        }
+
         /*
          * Invoke remote method to get package information and install
          * location values. Override install location based on default
@@ -7356,15 +7370,9 @@
                 Slog.w(TAG, "Conflicting flags specified for installing on both internal and external");
                 ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
             } else {
-                final long lowThreshold;
-
-                final DeviceStorageMonitorService dsm = (DeviceStorageMonitorService) ServiceManager
-                        .getService(DeviceStorageMonitorService.SERVICE);
-                if (dsm == null) {
+                final long lowThreshold = getMemoryLowThreshold();
+                if (lowThreshold == 0L) {
                     Log.w(TAG, "Couldn't get low memory threshold; no free limit imposed");
-                    lowThreshold = 0L;
-                } else {
-                    lowThreshold = dsm.getMemoryLowThreshold();
                 }
 
                 try {
@@ -7922,8 +7930,8 @@
         boolean checkFreeStorage(IMediaContainerService imcs) throws RemoteException {
             final long lowThreshold;
 
-            final DeviceStorageMonitorService dsm = (DeviceStorageMonitorService) ServiceManager
-                    .getService(DeviceStorageMonitorService.SERVICE);
+            final DeviceStorageMonitorInternal
+                    dsm = LocalServices.getService(DeviceStorageMonitorInternal.class);
             if (dsm == null) {
                 Log.w(TAG, "Couldn't get low memory threshold; no free limit imposed");
                 lowThreshold = 0L;
@@ -9738,10 +9746,10 @@
                 clearExternalStorageDataSync(packageName, userId, true);
                 if (succeeded) {
                     // invoke DeviceStorageMonitor's update method to clear any notifications
-                    DeviceStorageMonitorService dsm = (DeviceStorageMonitorService)
-                            ServiceManager.getService(DeviceStorageMonitorService.SERVICE);
+                    DeviceStorageMonitorInternal
+                            dsm = LocalServices.getService(DeviceStorageMonitorInternal.class);
                     if (dsm != null) {
-                        dsm.updateMemory();
+                        dsm.checkMemory();
                     }
                 }
                 if(observer != null) {
@@ -11566,12 +11574,17 @@
         return true;
     }
 
+    @Override
     public boolean isStorageLow() {
         final long token = Binder.clearCallingIdentity();
         try {
-            final DeviceStorageMonitorService dsm = (DeviceStorageMonitorService) ServiceManager
-                    .getService(DeviceStorageMonitorService.SERVICE);
-            return dsm.isMemoryLow();
+            final DeviceStorageMonitorInternal
+                    dsm = LocalServices.getService(DeviceStorageMonitorInternal.class);
+            if (dsm != null) {
+                return dsm.isMemoryLow();
+            } else {
+                return false;
+            }
         } finally {
             Binder.restoreCallingIdentity(token);
         }
diff --git a/services/java/com/android/server/power/DisplayPowerController.java b/services/java/com/android/server/power/DisplayPowerController.java
index 60d44c7..b8a78e3 100644
--- a/services/java/com/android/server/power/DisplayPowerController.java
+++ b/services/java/com/android/server/power/DisplayPowerController.java
@@ -16,10 +16,11 @@
 
 package com.android.server.power;
 
-import com.android.server.LightsService;
-import com.android.server.TwilightService;
-import com.android.server.TwilightService.TwilightState;
+import com.android.server.lights.LightsManager;
+import com.android.server.twilight.TwilightListener;
+import com.android.server.twilight.TwilightManager;
 import com.android.server.display.DisplayManagerService;
+import com.android.server.twilight.TwilightState;
 
 import android.animation.Animator;
 import android.animation.ObjectAnimator;
@@ -179,10 +180,10 @@
     private Handler mCallbackHandler;
 
     // The lights service.
-    private final LightsService mLights;
+    private final LightsManager mLights;
 
     // The twilight service.
-    private final TwilightService mTwilight;
+    private final TwilightManager mTwilight;
 
     // The display manager.
     private final DisplayManagerService mDisplayManager;
@@ -351,7 +352,7 @@
      * Creates the display power controller.
      */
     public DisplayPowerController(Looper looper, Context context, Notifier notifier,
-            LightsService lights, TwilightService twilight, SensorManager sensorManager,
+            LightsManager lights, TwilightManager twilight, SensorManager sensorManager,
             DisplayManagerService displayManager,
             SuspendBlocker displaySuspendBlocker, DisplayBlanker displayBlanker,
             Callbacks callbacks, Handler callbackHandler) {
@@ -528,7 +529,7 @@
     private void initialize() {
         mPowerState = new DisplayPowerState(
                 new ElectronBeam(mDisplayManager), mDisplayBlanker,
-                mLights.getLight(LightsService.LIGHT_ID_BACKLIGHT));
+                mLights.getLight(LightsManager.LIGHT_ID_BACKLIGHT));
 
         mElectronBeamOnAnimator = ObjectAnimator.ofFloat(
                 mPowerState, DisplayPowerState.ELECTRON_BEAM_LEVEL, 0.0f, 1.0f);
@@ -1368,8 +1369,7 @@
         }
     };
 
-    private final TwilightService.TwilightListener mTwilightListener =
-            new TwilightService.TwilightListener() {
+    private final TwilightListener mTwilightListener = new TwilightListener() {
         @Override
         public void onTwilightStateChanged() {
             mTwilightChanged = true;
diff --git a/services/java/com/android/server/power/DisplayPowerState.java b/services/java/com/android/server/power/DisplayPowerState.java
index fa318f8..42af4b4 100644
--- a/services/java/com/android/server/power/DisplayPowerState.java
+++ b/services/java/com/android/server/power/DisplayPowerState.java
@@ -16,7 +16,7 @@
 
 package com.android.server.power;
 
-import com.android.server.LightsService;
+import com.android.server.lights.Light;
 
 import android.os.AsyncTask;
 import android.os.Handler;
@@ -56,7 +56,7 @@
     private final Choreographer mChoreographer;
     private final ElectronBeam mElectronBeam;
     private final DisplayBlanker mDisplayBlanker;
-    private final LightsService.Light mBacklight;
+    private final Light mBacklight;
     private final PhotonicModulator mPhotonicModulator;
 
     private boolean mScreenOn;
@@ -72,7 +72,7 @@
     private Runnable mCleanListener;
 
     public DisplayPowerState(ElectronBeam electronBean,
-            DisplayBlanker displayBlanker, LightsService.Light backlight) {
+            DisplayBlanker displayBlanker, Light backlight) {
         mHandler = new Handler(true /*async*/);
         mChoreographer = Choreographer.getInstance();
         mElectronBeam = electronBean;
diff --git a/services/java/com/android/server/power/PowerManagerService.java b/services/java/com/android/server/power/PowerManagerService.java
index da9548f..13f55e2 100644
--- a/services/java/com/android/server/power/PowerManagerService.java
+++ b/services/java/com/android/server/power/PowerManagerService.java
@@ -20,10 +20,11 @@
 import com.android.internal.app.IBatteryStats;
 import com.android.server.BatteryService;
 import com.android.server.EventLogTags;
-import com.android.server.LightsService;
-import com.android.server.TwilightService;
+import com.android.server.lights.LightsService;
+import com.android.server.lights.Light;
+import com.android.server.lights.LightsManager;
+import com.android.server.twilight.TwilightManager;
 import com.android.server.Watchdog;
-import com.android.server.am.ActivityManagerService;
 import com.android.server.display.DisplayManagerService;
 import com.android.server.dreams.DreamManagerService;
 
@@ -168,7 +169,7 @@
     private static final int DREAM_BATTERY_LEVEL_DRAIN_CUTOFF = 5;
 
     private Context mContext;
-    private LightsService mLightsService;
+    private LightsManager mLightsManager;
     private BatteryService mBatteryService;
     private DisplayManagerService mDisplayManagerService;
     private IBatteryStats mBatteryStats;
@@ -181,7 +182,7 @@
     private WirelessChargerDetector mWirelessChargerDetector;
     private SettingsObserver mSettingsObserver;
     private DreamManagerService mDreamManager;
-    private LightsService.Light mAttentionLight;
+    private Light mAttentionLight;
 
     private final Object mLock = new Object();
 
@@ -396,11 +397,11 @@
      * Initialize the power manager.
      * Must be called before any other functions within the power manager are called.
      */
-    public void init(Context context, LightsService ls,
-            ActivityManagerService am, BatteryService bs, IBatteryStats bss,
+    public void init(Context context, LightsManager ls,
+            BatteryService bs, IBatteryStats bss,
             IAppOpsService appOps, DisplayManagerService dm) {
         mContext = context;
-        mLightsService = ls;
+        mLightsManager = ls;
         mBatteryService = bs;
         mBatteryStats = bss;
         mAppOps = appOps;
@@ -427,12 +428,12 @@
         }
     }
 
-    public void systemReady(TwilightService twilight, DreamManagerService dreamManager) {
+    public void systemReady(TwilightManager twilight, DreamManagerService dreamManager) {
         synchronized (mLock) {
             mSystemReady = true;
             mDreamManager = dreamManager;
 
-            PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
+            PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
             mScreenBrightnessSettingMinimum = pm.getMinimumScreenBrightnessSetting();
             mScreenBrightnessSettingMaximum = pm.getMaximumScreenBrightnessSetting();
             mScreenBrightnessSettingDefault = pm.getDefaultScreenBrightnessSetting();
@@ -448,7 +449,7 @@
             // The display power controller runs on the power manager service's
             // own handler thread to ensure timely operation.
             mDisplayPowerController = new DisplayPowerController(mHandler.getLooper(),
-                    mContext, mNotifier, mLightsService, twilight, sensorManager,
+                    mContext, mNotifier, mLightsManager, twilight, sensorManager,
                     mDisplayManagerService, mDisplaySuspendBlocker, mDisplayBlanker,
                     mDisplayPowerControllerCallbacks, mHandler);
 
@@ -456,7 +457,7 @@
                     createSuspendBlockerLocked("PowerManagerService.WirelessChargerDetector"),
                     mHandler);
             mSettingsObserver = new SettingsObserver(mHandler);
-            mAttentionLight = mLightsService.getLight(LightsService.LIGHT_ID_ATTENTION);
+            mAttentionLight = mLightsManager.getLight(LightsManager.LIGHT_ID_ATTENTION);
 
             // Register for broadcasts from other components of the system.
             IntentFilter filter = new IntentFilter();
@@ -2061,7 +2062,7 @@
     }
 
     private void setAttentionLightInternal(boolean on, int color) {
-        LightsService.Light light;
+        Light light;
         synchronized (mLock) {
             if (!mSystemReady) {
                 return;
@@ -2070,7 +2071,7 @@
         }
 
         // Control light outside of lock.
-        light.setFlashing(color, LightsService.LIGHT_FLASH_HARDWARE, (on ? 3 : 0), 0);
+        light.setFlashing(color, Light.LIGHT_FLASH_HARDWARE, (on ? 3 : 0), 0);
     }
 
     /**
diff --git a/services/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/java/com/android/server/statusbar/StatusBarManagerInternal.java
new file mode 100644
index 0000000..4f75189
--- /dev/null
+++ b/services/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -0,0 +1,29 @@
+/**
+ * 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.statusbar;
+
+import com.android.server.notification.NotificationDelegate;
+
+import android.os.IBinder;
+import android.service.notification.StatusBarNotification;
+
+public interface StatusBarManagerInternal {
+    void setNotificationDelegate(NotificationDelegate delegate);
+    IBinder addNotification(StatusBarNotification notification);
+    void updateNotification(IBinder key, StatusBarNotification notification);
+    void removeNotification(IBinder key);
+}
diff --git a/services/java/com/android/server/StatusBarManagerService.java b/services/java/com/android/server/statusbar/StatusBarManagerService.java
similarity index 80%
rename from services/java/com/android/server/StatusBarManagerService.java
rename to services/java/com/android/server/statusbar/StatusBarManagerService.java
index f207c08..2ae467e 100644
--- a/services/java/com/android/server/StatusBarManagerService.java
+++ b/services/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -14,26 +14,28 @@
  * limitations under the License.
  */
 
-package com.android.server;
+package com.android.server.statusbar;
 
 import android.app.StatusBarManager;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
 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.LocalServices;
+import com.android.server.notification.NotificationDelegate;
 import com.android.server.wm.WindowManagerService;
 
 import java.io.FileDescriptor;
@@ -51,31 +53,31 @@
 public class StatusBarManagerService extends IStatusBarService.Stub
     implements WindowManagerService.OnHardKeyboardStatusChangeListener
 {
-    static final String TAG = "StatusBarManagerService";
-    static final boolean SPEW = false;
+    private static final String TAG = "StatusBarManagerService";
+    private 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<IBinder,StatusBarNotification> mNotifications
+    private final Context mContext;
+    private final WindowManagerService mWindowManager;
+    private Handler mHandler = new Handler();
+    private NotificationDelegate mNotificationDelegate;
+    private volatile IStatusBar mBar;
+    private StatusBarIconList mIcons = new StatusBarIconList();
+    private HashMap<IBinder,StatusBarNotification> mNotifications
             = new HashMap<IBinder,StatusBarNotification>();
 
     // for disabling the status bar
-    final ArrayList<DisableRecord> mDisableRecords = new ArrayList<DisableRecord>();
-    IBinder mSysUiVisToken = new Binder();
-    int mDisabled = 0;
+    private final ArrayList<DisableRecord> mDisableRecords = new ArrayList<DisableRecord>();
+    private IBinder mSysUiVisToken = new Binder();
+    private int mDisabled = 0;
 
-    Object mLock = new Object();
+    private 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 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;
@@ -90,16 +92,6 @@
         }
     }
 
-    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
      */
@@ -110,15 +102,74 @@
 
         final Resources res = context.getResources();
         mIcons.defineSlots(res.getStringArray(com.android.internal.R.array.config_statusBarIcons));
+
+        LocalServices.addService(StatusBarManagerInternal.class, mInternalService);
     }
 
-    public void setNotificationCallbacks(NotificationCallbacks listener) {
-        mNotificationCallbacks = listener;
-    }
+    /**
+     * 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();
 
@@ -130,6 +181,7 @@
         }
     }
 
+    @Override
     public void collapsePanels() {
         enforceExpandStatusBar();
 
@@ -141,6 +193,7 @@
         }
     }
 
+    @Override
     public void expandSettingsPanel() {
         enforceExpandStatusBar();
 
@@ -152,6 +205,7 @@
         }
     }
 
+    @Override
     public void disable(int what, IBinder token, String pkg) {
         disableInternal(mCurrentUserId, what, token, pkg);
     }
@@ -177,7 +231,7 @@
             mDisabled = net;
             mHandler.post(new Runnable() {
                     public void run() {
-                        mNotificationCallbacks.onSetDisabled(net);
+                        mNotificationDelegate.onSetDisabled(net);
                     }
                 });
             if (mBar != null) {
@@ -189,6 +243,7 @@
         }
     }
 
+    @Override
     public void setIcon(String slot, String iconPackage, int iconId, int iconLevel,
             String contentDescription) {
         enforceStatusBar();
@@ -214,6 +269,7 @@
         }
     }
 
+    @Override
     public void setIconVisibility(String slot, boolean visible) {
         enforceStatusBar();
 
@@ -241,6 +297,7 @@
         }
     }
 
+    @Override
     public void removeIcon(String slot) {
         enforceStatusBar();
 
@@ -265,6 +322,7 @@
      * 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();
 
@@ -285,6 +343,7 @@
         }
     }
 
+    @Override
     public void setImeWindowStatus(final IBinder token, final int vis, final int backDisposition) {
         enforceStatusBar();
 
@@ -312,6 +371,7 @@
         }
     }
 
+    @Override
     public void setSystemUiVisibility(int vis, int mask) {
         // also allows calls from window manager which is in this process.
         enforceStatusBarService();
@@ -344,6 +404,7 @@
         }
     }
 
+    @Override
     public void setHardKeyboardEnabled(final boolean enabled) {
         mHandler.post(new Runnable() {
             public void run() {
@@ -426,6 +487,7 @@
     // ================================================================================
     // Callbacks from the status bar service.
     // ================================================================================
+    @Override
     public void registerStatusBar(IStatusBar bar, StatusBarIconList iconList,
             List<IBinder> notificationKeys, List<StatusBarNotification> notifications,
             int switches[], List<IBinder> binders) {
@@ -458,86 +520,64 @@
      * 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();
-
-        // tell the notification manager to turn off the lights.
-        mNotificationCallbacks.onPanelRevealed();
+        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();
-
-        mNotificationCallbacks.onNotificationClick(pkg, tag, id);
+        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();
-
-        // WARNING: this will call back into us to do the remove.  Don't hold any locks.
-        mNotificationCallbacks.onNotificationError(pkg, tag, id, uid, initialPid, message);
+        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();
-
-        mNotificationCallbacks.onNotificationClear(pkg, tag, id);
+        long identity = Binder.clearCallingIdentity();
+        try {
+            mNotificationDelegate.onNotificationClear(pkg, tag, id);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
+    @Override
     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;
+        long identity = Binder.clearCallingIdentity();
+        try {
+            mNotificationDelegate.onClearAll();
+        } finally {
+            Binder.restoreCallingIdentity(identity);
         }
     }
 
-    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
diff --git a/services/java/com/android/server/storage/DeviceStorageMonitorInternal.java b/services/java/com/android/server/storage/DeviceStorageMonitorInternal.java
new file mode 100644
index 0000000..a91a81b
--- /dev/null
+++ b/services/java/com/android/server/storage/DeviceStorageMonitorInternal.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.storage;
+
+public interface DeviceStorageMonitorInternal {
+    boolean isMemoryLow();
+    long getMemoryLowThreshold();
+    void checkMemory();
+}
+
diff --git a/services/java/com/android/server/DeviceStorageMonitorService.java b/services/java/com/android/server/storage/DeviceStorageMonitorService.java
similarity index 79%
rename from services/java/com/android/server/DeviceStorageMonitorService.java
rename to services/java/com/android/server/storage/DeviceStorageMonitorService.java
index 016c561..8805084 100644
--- a/services/java/com/android/server/DeviceStorageMonitorService.java
+++ b/services/java/com/android/server/storage/DeviceStorageMonitorService.java
@@ -14,7 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.server;
+package com.android.server.storage;
+
+import com.android.server.EventLogTags;
+import com.android.server.SystemService;
 
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -29,8 +32,8 @@
 import android.os.Environment;
 import android.os.FileObserver;
 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.StatFs;
@@ -66,13 +69,13 @@
  * 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";
+public class DeviceStorageMonitorService extends SystemService {
+    static final String TAG = "DeviceStorageMonitorService";
 
-    private static final boolean DEBUG = false;
-    private static final boolean localLOGV = false;
+    static final boolean DEBUG = false;
+    static final boolean localLOGV = false;
 
-    private static final int DEVICE_MEMORY_WHAT = 1;
+    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;
 
@@ -84,9 +87,8 @@
     private long mFreeMemAfterLastCacheClear;  // on /data
     private long mLastReportedFreeMem;
     private long mLastReportedFreeMemTime;
-    private boolean mLowMemFlag=false;
+    boolean mLowMemFlag=false;
     private boolean mMemFullFlag=false;
-    private Context mContext;
     private ContentResolver mResolver;
     private long mTotalMemory;  // on /data
     private StatFs mDataFileStats;
@@ -98,19 +100,19 @@
     private static final File CACHE_PATH = Environment.getDownloadCacheDirectory();
 
     private long mThreadStartTime = -1;
-    private boolean mClearSucceeded = false;
-    private boolean mClearingCache;
+    boolean mClearSucceeded = false;
+    boolean mClearingCache;
     private Intent mStorageLowIntent;
     private Intent mStorageOkIntent;
     private Intent mStorageFullIntent;
     private Intent mStorageNotFullIntent;
     private CachePackageDataObserver mClearCacheObserver;
-    private final CacheFileDeletedObserver mCacheFileDeletedObserver;
+    private 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;
+    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
@@ -126,13 +128,13 @@
     /**
      * This string is used for ServiceManager access to this class.
      */
-    public static final String SERVICE = "devicestoragemonitor";
+    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() {
+    private Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
             //don't handle an invalid message
@@ -144,7 +146,7 @@
         }
     };
 
-    class CachePackageDataObserver extends IPackageDataObserver.Stub {
+    private class CachePackageDataObserver extends IPackageDataObserver.Stub {
         public void onRemoveCompleted(String packageName, boolean succeeded) {
             mClearSucceeded = succeeded;
             mClearingCache = false;
@@ -154,7 +156,7 @@
         }
     }
 
-    private final void restatDataDir() {
+    private void restatDataDir() {
         try {
             mDataFileStats.restat(DATA_PATH.getAbsolutePath());
             mFreeMem = (long) mDataFileStats.getAvailableBlocks() *
@@ -206,7 +208,7 @@
         }
     }
 
-    private final void clearCache() {
+    private void clearCache() {
         if (mClearCacheObserver == null) {
             // Lazy instantiation
             mClearCacheObserver = new CachePackageDataObserver();
@@ -223,7 +225,7 @@
         }
     }
 
-    private final void checkMemory(boolean checkCache) {
+    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
@@ -300,7 +302,7 @@
         postCheckMemoryMsg(true, DEFAULT_CHECK_INTERVAL);
     }
 
-    private void postCheckMemoryMsg(boolean clearCache, long delay) {
+    void postCheckMemoryMsg(boolean clearCache, long delay) {
         // Remove queued messages
         mHandler.removeMessages(DEVICE_MEMORY_WHAT);
         mHandler.sendMessageDelayed(mHandler.obtainMessage(DEVICE_MEMORY_WHAT,
@@ -312,10 +314,10 @@
     * Constructor to run service. initializes the disk space threshold value
     * and posts an empty message to kickstart the process.
     */
-    public DeviceStorageMonitorService(Context context) {
+    @Override
+    public void onCreate(Context context) {
         mLastReportedFreeMemTime = 0;
-        mContext = context;
-        mResolver = mContext.getContentResolver();
+        mResolver = context.getContentResolver();
         //create StatFs object
         mDataFileStats = new StatFs(DATA_PATH.getAbsolutePath());
         mSystemFileStats = new StatFs(SYSTEM_PATH.getAbsolutePath());
@@ -331,9 +333,12 @@
         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(context);
+        final StorageManager sm = StorageManager.from(getContext());
         mMemLowThreshold = sm.getStorageLowBytes(DATA_PATH);
         mMemFullThreshold = sm.getStorageFullBytes(DATA_PATH);
 
@@ -345,6 +350,78 @@
 
         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));
     }
 
     /**
@@ -352,7 +429,8 @@
     * an error dialog indicating low disk space and launch the Installer
     * application
     */
-    private final void sendNotification() {
+    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);
@@ -363,86 +441,58 @@
         lowMemIntent.putExtra("memory", mFreeMem);
         lowMemIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         NotificationManager mNotificationMgr =
-                (NotificationManager)mContext.getSystemService(
+                (NotificationManager)context.getSystemService(
                         Context.NOTIFICATION_SERVICE);
-        CharSequence title = mContext.getText(
+        CharSequence title = context.getText(
                 com.android.internal.R.string.low_internal_storage_view_title);
-        CharSequence details = mContext.getText(
+        CharSequence details = context.getText(
                 com.android.internal.R.string.low_internal_storage_view_text);
-        PendingIntent intent = PendingIntent.getActivityAsUser(mContext, 0,  lowMemIntent, 0,
+        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(mContext, title, details, intent);
+        notification.setLatestEventInfo(context, title, details, intent);
         mNotificationMgr.notifyAsUser(null, LOW_MEMORY_NOTIFICATION_ID, notification,
                 UserHandle.ALL);
-        mContext.sendStickyBroadcastAsUser(mStorageLowIntent, UserHandle.ALL);
+        context.sendStickyBroadcastAsUser(mStorageLowIntent, UserHandle.ALL);
     }
 
     /**
      * Cancels low storage notification and sends OK intent.
      */
-    private final void cancelNotification() {
+    private void cancelNotification() {
+        final Context context = getContext();
         if(localLOGV) Slog.i(TAG, "Canceling low memory notification");
         NotificationManager mNotificationMgr =
-                (NotificationManager)mContext.getSystemService(
+                (NotificationManager)context.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);
+        context.removeStickyBroadcastAsUser(mStorageLowIntent, UserHandle.ALL);
+        context.sendBroadcastAsUser(mStorageOkIntent, UserHandle.ALL);
     }
 
     /**
      * Send a notification when storage is full.
      */
-    private final void sendFullNotification() {
+    private void sendFullNotification() {
         if(localLOGV) Slog.i(TAG, "Sending memory full notification");
-        mContext.sendStickyBroadcastAsUser(mStorageFullIntent, UserHandle.ALL);
+        getContext().sendStickyBroadcastAsUser(mStorageFullIntent, UserHandle.ALL);
     }
 
     /**
      * Cancels memory full notification and sends "not full" intent.
      */
-    private final void cancelFullNotification() {
+    private void cancelFullNotification() {
         if(localLOGV) Slog.i(TAG, "Canceling memory full notification");
-        mContext.removeStickyBroadcastAsUser(mStorageFullIntent, UserHandle.ALL);
-        mContext.sendBroadcastAsUser(mStorageNotFullIntent, UserHandle.ALL);
+        getContext().removeStickyBroadcastAsUser(mStorageFullIntent, UserHandle.ALL);
+        getContext().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 {
+    private static class CacheFileDeletedObserver extends FileObserver {
         public CacheFileDeletedObserver() {
             super(Environment.getDownloadCacheDirectory().getAbsolutePath(), FileObserver.DELETE);
         }
@@ -452,40 +502,4 @@
             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/twilight/TwilightListener.java b/services/java/com/android/server/twilight/TwilightListener.java
new file mode 100644
index 0000000..29ead44
--- /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 0000000..b3de58b
--- /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/TwilightService.java b/services/java/com/android/server/twilight/TwilightService.java
similarity index 74%
rename from services/java/com/android/server/TwilightService.java
rename to services/java/com/android/server/twilight/TwilightService.java
index 0356faa..8feb97b 100644
--- a/services/java/com/android/server/TwilightService.java
+++ b/services/java/com/android/server/twilight/TwilightService.java
@@ -14,7 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.server;
+package com.android.server.twilight;
+
+import com.android.server.SystemService;
+import com.android.server.TwilightCalculator;
 
 import android.app.AlarmManager;
 import android.app.PendingIntent;
@@ -34,9 +37,7 @@
 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;
@@ -47,78 +48,88 @@
  * 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 =
+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";
 
-    private final Context mContext;
-    private final AlarmManager mAlarmManager;
-    private final LocationManager mLocationManager;
-    private final LocationHandler mLocationHandler;
+    final Object mLock = new Object();
 
-    private final Object mLock = new Object();
+    AlarmManager mAlarmManager;
+    LocationManager mLocationManager;
+    LocationHandler mLocationHandler;
 
-    private final ArrayList<TwilightListenerRecord> mListeners =
+    final ArrayList<TwilightListenerRecord> mListeners =
             new ArrayList<TwilightListenerRecord>();
 
-    private boolean mSystemReady;
+    TwilightState mTwilightState;
 
-    private TwilightState mTwilightState;
-
-    public TwilightService(Context context) {
-        mContext = context;
-
-        mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
-        mLocationManager = (LocationManager)mContext.getSystemService(Context.LOCATION_SERVICE);
+    @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);
     }
 
-    void systemReady() {
-        synchronized (mLock) {
-            mSystemReady = true;
+    private static class TwilightListenerRecord implements Runnable {
+        private final TwilightListener mListener;
+        private final Handler mHandler;
 
-            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);
+        public TwilightListenerRecord(TwilightListener listener, Handler handler) {
+            mListener = listener;
+            mHandler = handler;
+        }
 
-            if (!mListeners.isEmpty()) {
-                mLocationHandler.enableLocationUpdates();
+        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;
             }
         }
-    }
 
-    /**
-     * 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.
+         */
+        @Override
+        public void registerListener(TwilightListener listener, Handler handler) {
+            synchronized (mLock) {
+                mListeners.add(new TwilightListenerRecord(listener, handler));
 
-    /**
-     * 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();
+                if (mListeners.size() == 1) {
+                    mLocationHandler.enableLocationUpdates();
+                }
             }
         }
-    }
+    };
 
     private void setTwilightState(TwilightState state) {
         synchronized (mLock) {
@@ -128,9 +139,10 @@
                 }
 
                 mTwilightState = state;
-                int count = mListeners.size();
-                for (int i = 0; i < count; i++) {
-                    mListeners.get(i).post();
+
+                final int listenerLen = mListeners.size();
+                for (int i = 0; i < listenerLen; i++) {
+                    mListeners.get(i).postUpdate();
                 }
             }
         }
@@ -162,124 +174,6 @@
         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;
@@ -518,11 +412,12 @@
             }
 
             Intent updateIntent = new Intent(ACTION_UPDATE_TWILIGHT_STATE);
-            PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, updateIntent, 0);
+            PendingIntent pendingIntent = PendingIntent.getBroadcast(
+                    getContext(), 0, updateIntent, 0);
             mAlarmManager.cancel(pendingIntent);
             mAlarmManager.setExact(AlarmManager.RTC, nextUpdate, pendingIntent);
         }
-    };
+    }
 
     private final BroadcastReceiver mUpdateLocationReceiver = new BroadcastReceiver() {
         @Override
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 0000000..91e24d7
--- /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))
+                + "}";
+    }
+}